質問
ダウンキャスト以外の基本クラスのリストをUIにバインドするより良い方法があります。例:
static void Main(string[] args) {
List<Animal> list = new List<Animal>();
Pig p = new Pig(5);
Dog d = new Dog("/images/dog1.jpg");
list.Add(p);
list.Add(d);
foreach (Animal a in list)
{
DoPigStuff(a as Pig);
DoDogStuff(a as Dog);
}
}
static void DoPigStuff(Pig p)
{
if (p != null)
{
label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
}
}
static void DoDogStuff(Dog d) {
if (d != null)
{
Image1.src = d.Image;
}
}
class Animal {
public String Name { get; set; }
}
class Pig : Animal{
public int TailLength { get; set; }
public Pig(int tailLength)
{
Name = "Mr Pig";
TailLength = tailLength;
}
}
class Dog : Animal {
public String Image { get; set; }
public Dog(String image)
{
Name = "Mr Dog";
Image = image;
}
}
解決
この種の問題に直面したとき、訪問者パターンに従います。
>interface IVisitor
{
void DoPigStuff(Piggy p);
void DoDogStuff(Doggy d);
}
class GuiVisitor : IVisitor
{
void DoPigStuff(Piggy p)
{
label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
}
void DoDogStuff(Doggy d)
{
Image1.src = d.Image;
}
}
abstract class Animal
{
public String Name { get; set; }
public abstract void Visit(IVisitor visitor);
}
class Piggy : Animal
{
public int TailLength { get; set; }
public Piggy(int tailLength)
{
Name = "Mr Pig";
TailLength = tailLength;
}
public void Visit(IVisitor visitor)
{
visitor.DoPigStuff(this);
}
}
class Doggy : Animal
{
public String Image { get; set; }
public Doggy(String image)
{
Name = "Mr Dog";
Image = image;
}
public void Visit(IVisitor visitor)
{
visitor.DoDogStuff(this);
}
}
public class AnimalProgram
{
static void Main(string[] args) {
List<Animal> list = new List<Animal>();
Pig p = new Pig(5);
Dog d = new Dog("/images/dog1.jpg");
list.Add(p);
list.Add(d);
IVisitor visitor = new GuiVisitor();
foreach (Animal a in list)
{
a.Visit(visitor);
}
}
}
したがって、ビジターパターンは、Java、Smalltalk、C#、C ++などの従来の単一ディスパッチオブジェクト指向言語での二重ディスパッチをシミュレートします。
jop 'に対するこのコードの唯一の利点sは、新しいタイプの訪問者( XmlSerializeVisitor や FeedAnimalVisitor など)を追加する必要があるときに、IVisitorインターフェイスを後で別のクラスに実装できることです。
>他のヒント
どうしてAnimalに、PigとDogに実装を強制する抽象メソッドを含めないのか
public class Animal
{
public abstract void DoStuff();
}
public Dog : Animal
{
public override void DoStuff()
{
// Do dog specific stuff here
}
}
public Pig : Animal
{
public override void DoStuff()
{
// Do pig specific stuff here
}
}
このようにして、特定の各クラスがそのアクションに対して責任を負い、コードがより簡単になります。また、foreachループ内でキャストする必要もありません。
これを行う別の方法は、メソッドを呼び出す前に型チェックを実行することです:
if (animal is Pig) DoPigStuff();
if (animal is Dog) DoDogStuff();
探しているのは複数のディスパッチです。いいえ-C#は複数ディスパッチをサポートしません。シングルディスパッチのみをサポートします。 C#は、レシーバーのタイプ(つまり、メソッド呼び出しの。の左側にあるオブジェクト)に基づいて、メソッドを動的にのみ呼び出すことができます
このコードは、 double-dispatch を使用します。コードにそれ自体を語らせます:
class DoubleDispatchSample
{
static void Main(string[]args)
{
List<Animal> list = new List<Animal>();
Pig p = new Pig(5);
Dog d = new Dog(@"/images/dog1.jpg");
list.Add(p);
list.Add(d);
Binder binder = new Binder(); // the class that knows how databinding works
foreach (Animal a in list)
{
a.BindoTo(binder); // initiate the binding
}
}
}
class Binder
{
public void DoPigStuff(Pig p)
{
label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
}
public void DoDogStuff(Dog d)
{
Image1.src = d.Image;
}
}
internal abstract class Animal
{
public String Name
{
get;
set;
}
protected abstract void BindTo(Binder binder);
}
internal class Pig : Animal
{
public int TailLength
{
get;
set;
}
public Pig(int tailLength)
{
Name = "Mr Pig";
TailLength = tailLength;
}
protected override void BindTo(Binder binder)
{
// Pig knows that it's a pig - so call the appropriate method.
binder.DoPigStuff(this);
}
}
internal class Dog : Animal
{
public String Image
{
get;
set;
}
public Dog(String image)
{
Name = "Mr Dog";
Image = image;
}
protected override void BindTo(Binder binder)
{
// Pig knows that it's a pig - so call the appropriate method.
binder.DoDogStuff(this);
}
}
注:サンプルコードはこれよりもはるかに単純です。ダブルディスパッチは、C#プログラミングの重武装の1つであると考えています。最後の手段としてのみ取り上げます。ただし、多くの種類のオブジェクトと、さまざまな種類のバインディングが必要な場合(たとえば、HTMLページにバインドする必要があるが、WinForms、レポート、またはCSVにバインドする必要がある場合) 、最終的にコードをリファクタリングしてダブルディスパッチを使用します。
基本クラスを十分に活用していない。 Animalクラスに仮想関数があった場合、Dog&amp;豚のオーバーライド、何もキャストする必要はありません。
より具体的な例がない限り、単にToString()をオーバーライドします。
ファクトリに関連付けられたビュークラスが必要だと思います。
Dictionary<Func<Animal, bool>, Func<Animal, AnimalView>> factories;
factories.Add(item => item is Dog, item => new DogView(item as Dog));
factories.Add(item => item is Pig, item => new PigView(item as Pig));
その後、DogViewとPigViewは次のようなAnimalViewを継承します。
class AnimalView {
abstract void DoStuff();
}
あなたは次のようなことをすることになります:
foreach (animal in list)
foreach (entry in factories)
if (entry.Key(animal)) entry.Value(animal).DoStuff();
これは戦略パターンの実装であるとも言えると思います。