- PR -

引数であるobject型を復元する方法(C#)

投稿者投稿内容
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-03-20 01:15
メソッドの引数であるobjectを復元して適切な処理を選択したいです。
より洗練された実装方法はありますか?
(添付ロジックを参照願います)

<不満に思う点>
ロジックが正常に機能するかどうかは、AddMain()のGetType().nameの判定が
正しいことを前提にしています。これではバグが入り込む危険性があります。
ただし、引数の柔軟性は確保したいのでobjectによる引渡しを放棄して
メソッドをoverrideする手法は採用したくありません。

質問
AddMain()にバグが入り込み難い実装方法はあるでしょうか?
GetType()およびTypeクラス
を想定しての質問ですが、
優れたアイデアであれば特定の手法に固執はしません。

/// <summary>
/// リフレクション実行例
/// </summary>
public class Class1
{
// Form1.csの記述
// Class1 cs1 = new Class1();
// string str = cs1.Example();
// textBox1.Text = str;
// textBox1.Update();
// Example() 実行結果
// 123+456
// 579

public class csStr
{
private string x;
private string y;
internal csStr(string px,string py)
{
this.x = px;
this.y = py;
}
internal string Add()
{
return this.x+"+"+this.y;
}
}
public class csInt
{
private int x;
private int y;
internal csInt(int px,int py)
{
this.x = px;
this.y = py;
}
internal string Add()
{
int temp = this.x+this.y;
return temp.ToString();
}
}
public string Example()
{
const string NewLine = "\\r\\n";
csStr cs1 = new csStr("123","456");
csInt cs2 = new csInt(123,456);
return MainAdd(cs1)+NewLine+MainAdd(cs2)+NewLine;
}

private string MainAdd(object obj)
{
string ans ="";
string temp = obj.GetType().Name;
switch(temp)
{
case "csStr":
{
// (csStr)の整合性をコンパイル時にチェックできない
csStr cs = (csStr)obj;
ans = cs.Add();
}
break;
case "csInt":
{
// (csInt)の整合性をコンパイル時にチェックできない
csInt cs = (csInt)obj;
ans = cs.Add();
}
break;
}
return ans;
}
}

なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-03-20 01:36
やりたいことにやや疑問があるのでこんなのでいいのか分かりませんが…
例えばインターフェイスを使って、
コード:

public interface IAddable
{
string Add()
}

public AddableStr : IAddable
{
:
string Add()
{
return this.x + "+" + this.y;
}
}

public AddableInt : IAddable
{
:
string Add()
{
int temp = this.x + this.y;
return temp.ToString();
}
}

public string Example()
{
const string NewLine = "\r\n";
AddableStr cs1 = new AddableStr("123", "456");
AddableStr cs2 = new AddableInt(123, 456);
return cs1.Add() + NewLine + cs2.Add() + NewLine;
}


とか。
別にMainAdd(AddMain?)は要らないと思いますが、もし定義するとすれば、
コード:

private string MainAdd(IAddable addable)
{
return addable.Add();
}

:
// Example
return MainAdd(cs1) + NewLine + MainAdd(cs2) + NewLine;



--追記
>ただし、引数の柔軟性は確保したいのでobjectによる引渡しを放棄して
>メソッドをoverrideする手法は採用したくありません。
てかこういう事がしたくないってことですか…?


[ メッセージ編集済み 編集者: なちゃ 編集日時 2004-03-20 01:38 ]
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2004-03-20 11:12
もしMainAdd()が「渡された引数のAdd()を呼び出す」という機能ならば、なちゃさんの仰るようにインターフェースを実装するのが良いと思います。
そしてその場合はMainAddは要りません。直接オブジェクトのAdd()を呼んでください。

これがもし、「Intだったらこういう処理をする」「ArrayListだったらこういう処理をする」
という風にライブラリに元々あるオブジェクトも処理するつもりなら、MainAdd()をオーバーロード(多重定義)してください。

これ以降はひろしさんの
>メソッドをoverrideする手法は
はオーバーロードの間違いだったと勝手に推測して話を進めさせていただきますが、ひろしさんは「オーバーロードすると柔軟性が失われる」というのは、例えばどのような場合を想定されていますか?

object型の引数を受け取るメソッドを一つだけ定義し、その中で「Intの時はこうする」「csStrのときはこうする」「csIntのときはこうする」と処理を分けようが、あるいは渡される型の数だけメソッドを(多重)定義しようが、結局想定されていない型のオブジェクトには対応できませんよね。
そして対応するオブジェクトに関しては、「どのように処理するか書かなければいけない」点についても同じです。
例えば、「今度doubleにも対応することになった」と変更になった場合に、いずれの方法をとっていてもdoubleの場合の処理を書かなければ対応できないということです。

「たくさんの種類の型に対応しなければならないのでメソッドがそれだけたくさんになってしまう」「後から対応する型を増やすたびにどんどんメソッドが増えていく」というのが嫌なのかもしれませんが、「MessageBox.Show()メソッドはたくさんあってもう訳分かんない」と言っている人をいまだかつて見たことはありません。

ひろしさんの提示されたソースは、説明する為に必要な部分だけを抜き出したものだと思いますので、実際何をするためのものなのか分からないので断定はできませんが、オーバーロードをするのが最も洗練された実装方法だと思います。


余談ですが、C#にはis演算子というものがあります。
GetType().Nameではなくてこちらを使ってみてはいかがでしょうか。
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-03-20 12:23
ご返答ありがとうございます。
未だ、interfaceを使っていなかったのでその点でとても参考になりました。
なかなかうまく説明できませんが、補足させていただきます。

MainAdd()でやりたかったこと
「個別の型(例題ではクラス)に関する記述を排除して共通の実行手順を定義する方法は
ないだろうか?」

例題では、MainAdd()が受け取る型がcsInt,csStrの2クラスだけでした。
これが10クラスに増えたとします。
そして、各クラスが実装しているメソッドもAdd()だけでなく5種類に増えたとします。
ところで、このメソッドには10種類のクラス共通の実行手順が定義できるとします。
このケースでは共通の実行手順だけを抽出して部品化するのが自然に思えます。
object型によるパラメータ引渡しとGetType()にこだわったのは上記の背景からです。

質問1
object渡しにこだわった場合ですが、
GetType()に多くのメンバーあって使いこなせません。
けれども、GetType().nameによる型の判別では危なっかしく感じます。
確実な判別をするにはどのGetType()メンバーを採用したら良いでしょうか?

質問2
そもそも、型ごとの分岐(switch文)を使いたくないのです。
objetctに格納されている「型」そのものを生成できないのでしょうか?
あるいは、object渡しにこだわらずに上記の目的を達成することは可能でしょうか?

-追記-
わかりにくい説明+ソースコードですみません。
ところで、掲載時にソースコードの空白が消えないようにするには
どうすれば良いのでしょうか?
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-03-20 13:03
一郎さん、ご返答ありがとうございます。
かぶってしまいました。(すみません)
一郎さんが推察された通りのことを私は感じていました。
私が模索している手法には無理があるということですね。
Jubei
ぬし
会議室デビュー日: 2002/03/02
投稿数: 830
お住まい・勤務地: 関西
投稿日時: 2004-03-20 13:07
諸農です。

なちゃさんとほぼ同じになりそうなんですが
インタフェースを定義しておいて、共通ルーチン(MainAdd?)で
インタフェースを抽出して処理すればいいのではないでしょうか。

もちろん。インタフェースを実装していないクラスでは処理は行え
ませんので、目的にかなっているのではないかと思います。

コード:
public interface ICommon
{
  string Add();
}

public class csStr:ICommon
{
  private string FA,FB;
  public csStr(string A,string B)
  {
     FA = A;
     FB = B;
  }
  public string Add()
  {
     return FA + FB;
  }
}

public class csInt:ICommon
{
  private int FA,FB;
  public csInt(int A,int B)
  {
     FA = A;
     FB = B;
  }
  public string Add()
  {
     return (FA + FB).ToString();
  }
}

public class csOther
{
  private double FA,FB;
  public csOther(double A,double B)
  {
     FA = A;
     FB = B;
  }
  public string Add()
  {
     return (FA + FB).ToString();
  }
}

private string CommonAdd(object AObject)
{
  ICommon c = AObject as ICommon;
  if (c != null) return c.Add();
  else return "ICommonインタフェースがありません";

}

private void button1_Click(object sender, System.EventArgs e)
{
  csStr s = new csStr("123","789");
  MessageBox.Show(CommonAdd(s));

  csInt i = new csInt(123,789);
  MessageBox.Show(CommonAdd(i));

  csOther o = new csOther(11.1F,22.2F);
  MessageBox.Show(CommonAdd(o));

}




引用:

-追記-
わかりにくい説明+ソースコードですみません。
ところで、掲載時にソースコードの空白が消えないようにするには
どうすれば良いのでしょうか?



codeタグで括ります。

ではでは(^^)/
_________________
諸農和岳
Powered by Turbo Delphi & Microsoft Visual Studio 2005

十兵衛@わんくま同盟
http://blogs.wankuma.com/jubei/
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-03-20 13:09
引用:

ひろしさんの書き込み (2004-03-20 12:23) より:
MainAdd()でやりたかったこと
「個別の型(例題ではクラス)に関する記述を排除して共通の実行手順を定義する方法は
ないだろうか?」

例題では、MainAdd()が受け取る型がcsInt,csStrの2クラスだけでした。
これが10クラスに増えたとします。
そして、各クラスが実装しているメソッドもAdd()だけでなく5種類に増えたとします。
ところで、このメソッドには10種類のクラス共通の実行手順が定義できるとします。
このケースでは共通の実行手順だけを抽出して部品化するのが自然に思えます。
object型によるパラメータ引渡しとGetType()にこだわったのは上記の背景からです。


実現方法は色々あるでしょうけど、ぱっと思いつくのは例えば、テンプレート内に発生する各操作をインターフェイスで定義して、MainAddを共通手順のテンプレートとする。
まあ、最初に書いたインターフェイスを使う方法の応用ですが。
コード:

public interface IProcedurable
{
string Method1()
string Method2()
string Method3()
:
}
public ProcedurableInt : IProcedurable
{
:
string Method1() {/* 手順1の具体的な操作 */}
string Method2() {/* 手順2の具体的な操作 */}
string Method3() {/* 手順3の具体的な操作 */}
:
}

public ProcedurableStr : IProcedurable {/* 同様に定義 */}

public string MainAdd(IProcedurable procedurable)
{
:
procedurable.Method1()
:
procedurable.Method2()
:
procedurable.Method3()
:
}


# とりあえず書いただけなので各メソッドの型とかは気にせずに…
見てのとおり、MainAddは共通の操作手順を実装しており、各手順における具体的な動作はパラメータの各クラスで実装します。これで、共通の操作を可能にしたいクラスはIProcedurableを実装するというルールになります。
# IProcedurableを実装するクラスは、共通手順による操作が可能ともいえる

要件によってこんなので済むのか、もっといろいろ考えないといけないのかなどはあるでしょうけど。

引用:

質問1
けれども、GetType().nameによる型の判別では危なっかしく感じます。
確実な判別をするにはどのGetType()メンバーを採用したら良いでしょうか?


型を厳密に判定するには、その型のType同士が同じインスタンスである事を確認すればよいので、
if (obj.GetType() == typeof(TargetType)) …
とかでいけます(Typeを元に判別する場合の話)。ただし、これは継承関係とかにあっても違う型と判別されるので、場合によってはIsAsignable(だったかな?)とかIsSubClassOf(だったかな?)とか(インスタンスを実装しているか調べるのもあったかな?)を使います。

引用:

質問2
そもそも、型ごとの分岐(switch文)を使いたくないのです。


通常は、そういう目的のために使えるのが継承またはインターフェイスの実装によって得られるポリモーフィズムの機構です(インターフェイスを使った一例がサンプルのコードに示した物です)。

引用:

ところで、掲載時にソースコードの空白が消えないようにするには
どうすれば良いのでしょうか?


BBコードのCODEってのを使います。投稿時に下のリンクを開けば載ってます。

--追記
微妙にかぶった…

[ メッセージ編集済み 編集者: なちゃ 編集日時 2004-03-20 13:15 ]
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-03-20 16:18
ご回答ありがとうございました。
interfaceの使ってみようと思います。

スキルアップ/キャリアアップ(JOB@IT)