【问题标题】:c# downcasting when binding to and interfacec#绑定到和接口时向下转换
【发布时间】:2010-09-13 21:52:46
【问题描述】:

除了向下转换之外,是否有更好的方法将基类列表绑定到 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;
    }
}

【问题讨论】:

  • 基本上,您似乎在问是否有某种方法可以为列表中的每种类型的项目提供不同的绑定信息。除了你正在做的事情之外,没有。

标签: c# casting oop


【解决方案1】:

为什么不让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 仅在课程范围内处理,这是设计课程的理想方式。我问的是绑定到用户界面。
【解决方案2】:

遇到此类问题,我关注visitor pattern

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)。

【讨论】:

    【解决方案3】:

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

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

    您正在寻找的是多次调度。 NO - 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# 编程中的重炮之一——我只是在万不得已的情况下才拿出它。但是如果有很多类型的对象和很多不同类型的绑定你需要做(例如你需要将它绑定到一个 HTML 页面但你还需要将它绑定到一个 WinForms 或一个报告或一个 CSV) ,我最终会重构我的代码以使用双重调度。

    【讨论】:

      【解决方案4】:

      你没有充分利用你的基类。如果你的 Animal 类中有一个被 Dog 和 Pig 覆盖的虚函数,你就不需要强制转换任何东西。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • 嗯,是的,在字符串中同时使用这两个属性是我的示例的问题......这不是我真正想要的
        【解决方案6】:

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

        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。使用更好的排序,或者改为使用访客。
        • 好点!我会选择订购,因为其中一个限制是不要在域模型中添加 UI 代码。
        猜你喜欢
        • 2021-11-08
        • 2016-08-17
        • 2014-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-05
        相关资源
        最近更新 更多