views:

3683

answers:

7

In C#, was is the best way to access a property of the derived class when the generic list contains just the base class.

public class ClassA : BaseClass
{
   public object PropertyA { get; set; }
}

public class ClassB: BaseClass
{
    public object PropertyB { get; set; }
}

public class BaseClass
{
}

public void Main
{
    List<BaseClass> MyList = new List<BaseClass>();
    ClassA a = new ClassA();
    ClassB b = new ClassB();

    MyList.Add(a);
    MyList.Add(b);

    for(int i = 0; i < MyList.Count; i++)
    {
        //I would like to access PropertyA abd PropertyB from the derived classes        
    }
}
+1  A: 

The whole premise doesn't make sense - what would PropertyB be for the a instance?

You can do this if you do manual runtime type checking (inst is Foo), and then cast to the type with the property you want.

Adam Wright
+1  A: 
   BaseClass o = MyList[i];
   if (o is ClassB)
   {
      object k = ((ClassB)o).PropertyB;
   }
   if (o is ClassA))
   {
      object j = ((ClassA)o).PropertyA;
   }
tvanfosson
A: 

You might have some issues with Generics and subclasses (in which case you should go back to System.Collections.ArrayList), but you have to cast the BaseClass to the subclass you wish to use. If you use the 'as' directory, it will succeed if the BaseClass can be casted to the subclass, or it will be null if it cannot be cast. It would look something like:

for(int i = 0; i < MyList.Count; i++)
{
    BaseClass bc = MyList[i];
    ClassA a = bc as ClassA;
    ClassB b = bc as ClassB;
    bc.BaseClassMethod();
    if (a != null) {
       a.PropertyA;
    }
    if (b != null) {
       b.PropertyB;
    }
}

Also, I should mention that this smells a bit bad. This is the kind of code that indicates a poorly structured object heirarchy. In general, if you can't say a IS A BaseClass, your design is probably wrong. But, hope that helps!

Travis
+6  A: 

Certainly you can downcast, like so:

for (int i = 0; i < MyList.Count; i++)
{
    if (MyList[i] is ClassA)
    {
        var a = ((ClassA)MyList[i]).PropertyA;
        // do stuff with a
    }

    if (MyList[i] is ClassB)
    {
        var b = ((ClassB)MyList[i]).PropertyB;
        // do stuff with b
    }
}

... However, you should take another look at what you're trying to accomplish. If you have common code that needs to get to properties of ClassA and ClassB, then you may be better off wrapping access to those properties up into a shared, virtual property or method in the ancestor class.

Something like:

public class BaseClass
{
    public virtual void DoStuff() { }
}

public class ClassA : BaseClass
{
    public object PropertyA { get; set; }

    public override void DoStuff() 
    {
        // do stuff with PropertyA 
    }
}

public class ClassB : BaseClass
{
    public object PropertyB { get; set; }

    public override void DoStuff() 
    {
        // do stuff with PropertyB
    }
}
Matt Hamilton
I used the second code sample to access my properties in my derived classes......Thanks for you input.
Michael Kniskern
A: 

If you are doing this a lot, another option would be to create an extension method on the list to give you back the correctly typed enumeration. i.e.

  public static class MyBaseListExtensions
  {
    public static IEnumerable<ClassA> GetAllAs(this List<MyBaseClass> list)
    {
      foreach (var obj in list)
      {
        if (obj is ClassA)
        {
          yield return (ClassA)obj;
        }
      }
    }

    public static IEnumerable<ClassB> GetAllbs(this List<MyBaseClass> list)
    {
      foreach (var obj in list)
      {
        if (obj is ClassB)
        {
          yield return (ClassB)obj;
        }
      }
    }
  }

Then you could use it like....

private void button1_Click(object sender, EventArgs e)
{
  ClassA a1 = new ClassA() { PropertyA = "Tim" };
  ClassA a2 = new ClassA() { PropertyA = "Pip" };
  ClassB b1 = new ClassB() { PropertyB = "Alex" };
  ClassB b2 = new ClassB() { PropertyB = "Rachel" };

  List<MyBaseClass> list = new List<MyBaseClass>();
  list.Add(a1);
  list.Add(a2);
  list.Add(b1);
  list.Add(b2);

  foreach (var a in list.GetAllAs())
  {
    listBox1.Items.Add(a.PropertyA);
  }

  foreach (var b in list.GetAllbs())
  {
    listBox2.Items.Add(b.PropertyB);
  }
}
Tim Jarvis
A: 

You would need to have the properties be declared as virtual on the base class and then override them in the derived class.

Ex:

public class ClassA : BaseClass
{
   public override object PropertyA { get; set; }
}

public class ClassB: BaseClass
{
    public override object PropertyB { get; set; }
}

public class BaseClass
{
    public virtual object PropertyA { get; set; }
    public virtual object PropertyB { get; set; }
}

public void Main
{
    List<BaseClass> MyList = new List<BaseClass>();
    ClassA a = new ClassA();
    ClassB b = new ClassB();

    MyList.Add(a);
    MyList.Add(b);

    for(int i = 0; i < MyList.Count; i++)
    {
     // Do something here with the Property
        MyList[i].PropertyA;
        MyList[i].PropertyB;      
    }
}

You would either need to implement the property in the base class to return a default value (such as null) or to make it abstract and force all the derived classes to implement both properties.

You should also note that you could return different things for say PropertyA by overrideing it in both derived classes and returning different values.

+2  A: 

Further to TimJ's answer, you can write one extension method that will work for all types:

public static IEnumerable<T> OfType<T>(this IEnumerable list)
{
    foreach (var obj in list)
    {
        if (obj is T)
            yield return (T)obj;
    }
}

Or if you have Linq, that function is in the namespace System.Linq.

Daniel Earwicker
I like this solution also....Thanks for the information
Michael Kniskern