C#中的向下转型在绑定接口时是什么意思?

3

有没有比向下转换(downcasting)更好的方法来将基类列表绑定到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;
    }
}

基本上,您似乎在询问是否有一种方法可以为列表中的每种类型的项目提供不同的绑定信息。除了像您所做的那样,没有其他方法。 - Jonathan Rupp
6个回答

7
为什么不让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循环中进行强制类型转换。


谢谢回复,虽然这是设计类的理想方式,如果DoStuff只在类的范围内处理的话。但我正在询问绑定到用户界面的问题。 - Corin Blaikie

6
当遇到这种问题时,我会遵循访问者模式
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的代码相比,唯一的优点是当您需要添加新类型的访问者(例如XmlSerializeVisitorFeedAnimalVisitor)时,可以稍后在不同的类上实现IVisitor接口。

4

另一种方法是在调用方法之前执行类型检查:

if (animal is Pig) DoPigStuff();
if (animal is Dog) DoDogStuff();

你需要的是多重分派。不,C# 不支持多重分派,它只支持单一分派。C# 只能根据接收器类型(即方法调用中左侧对象)动态调用方法。
这段代码使用了双重分派。我会让代码自己说明:
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#编程中的重型武器之一——只有作为最后的手段才会使用它。但是,如果有很多类型的对象和很多不同类型的绑定需要做(例如,您需要将其绑定到HTML页面,但也需要将其绑定到WinForms或报表或CSV),我最终会重构我的代码以使用双重分派。


0

你没有充分利用你的基类。如果你在Animal类中有一个虚函数,Dog和Pig覆盖它,你就不需要进行任何转换。


0
除非您有更具体的示例,否则只需覆盖 ToString()。

嗯,是的,在字符串中同时使用这两个属性是我的示例中存在的问题...那不是我真正想要的。 - Corin Blaikie

0

我认为你想要一个与工厂相关联的视图类。

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();

我想你也可以说这是策略模式的一种实现。

这个想法的缺点是它不能很好地处理继承级别。添加类BaXuyen:Pig后,工厂可能按照以下顺序迭代:[Pig, Dog, BaXuyen]。BaXuyen会错误地分派到一个普通的PigView而不是BaXuyenView。使用更好的排序或者访问者模式来解决。 - Jacob Krall
好观点!我会选择排序,因为其中一个限制条件是不在领域模型中添加UI代码。 - Hallgrim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接