tags:

views:

984

answers:

7

Is there a better way of binding a list of base class to a UI other than downcasting e.g:

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;
    }
}
+7  A: 

Why not make Animal include an abstract method that Pig and Dog are forced to implement

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
    }
}

This way each specific class takes responsibility for its actions, making your code simpler. You also won't need to cast inside your foreach loop.

Dan Herbert
Thanks for the reply,Although this is the ideal way to design the class if DoStuff was only dealing within the confines of the class. I am asking about binding to a user interface.
Corin
A: 

You're not taking full advantage of your base class. If you had a virtual function in your Animal class that Dog & Pig override, you wouldn't need to cast anything.

Austin Salonen
A: 

Sorry I don't think I made this clear enough that I was asking about the specific situation of binding to a user interface. Obviously sticking UI code inside the class would be a bad idea.

Corin
A: 

Unless you have a more specific example, just override ToString().

Will
Hmmm, yes having both the properties being used in a string is a problem with my example... that is not really what I'm after
Corin
+4  A: 

Another way to do this is to perform a typecheck before calling the method:

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

What you are looking for is multiple-dispatch. NO - C# doesn't support multiple-dispatch. It only supports single-dispatch. C# can only dynamically invoke a method based on the type of the receiver (i.e. the object at the left hand side of the . in the method call)

This code uses double-dispatch. I'll let the code speak for itself:

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

NOTE: Your sample code is much more simpler than this. I think of double-dispatch as one of the heavy artilleries in C# programming - I only take it out as a last resort. But if there are a lot of types of objects and a lot different types of bindings that you need to do (e.g. you need to bind it to an HTML page but you also need to bind it to a WinForms or a report or a CSV), I would eventually refactor my code to use double-dispatch.

jop
+5  A: 

When faced with this type of problem, I follow the 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);
    }  
  }
}

Thus the visitor pattern simulates double dispatch in a conventional single-dispatch object-oriented language such as Java, Smalltalk, C#, and C++.

The only advantage of this code over jop's is that the IVisitor interface can be implemented on a different class later when you need to add a new type of visitor (like a XmlSerializeVisitor or a FeedAnimalVisitor).

Jacob
A: 

I think you want a view-class associated with a factory.

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

Then your DogView and PigView will inherit AnimalView that looks something like:

class AnimalView {
  abstract void DoStuff();
}

You will end up doing something like:

foreach (animal in list)
  foreach (entry in factories)
    if (entry.Key(animal)) entry.Value(animal).DoStuff();

I guess you could also say that this is a implementation of the strategy pattern.

Hallgrim
This idea has the downside that it doesn't handle levels of inheritance well. Adding the class BaXuyen:Pig, factories may iterate in this order: [Pig, Dog, BaXuyen]. The BaXuyen would mistakenly dispatch to a plain old PigView instead of BaXuyenView. Use better ordering, or a Visitor instead.
Jacob
Good point! I would go with ordering, since one of the constraints was to not add UI code in the domain model.
Hallgrim