在C#中从基类访问派生类的属性

13
在C#中,当泛型列表只包含基类时,访问派生类的属性的最佳方式是什么?
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        
    }
}
7个回答

23

当然,你可以进行向下转换(downcast),例如:

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

然而,您应该再次审视您试图实现的目标。如果您有需要访问ClassA和ClassB属性的常用代码,那么最好将对这些属性的访问封装到祖先类中共享的虚拟属性或方法中。

例如:

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

我可能漏掉了什么,但是如果你只是在派生类中编写DoStuff(),为什么需要在BaseClass中使用它呢?难道不应该使用接口来强制要求在派生类中包含DoStuff()吗? - SteveCinq
因为这样,你可以在不知道类的确切派生类型的情况下,在BaseClass对象上调用该方法。 - John Willemse

5

除了TimJ的回答外,您可以编写一个扩展方法,适用于所有类型:

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

如果您使用Linq,那么该函数位于命名空间System.Linq中。


2
   BaseClass o = MyList[i];
   if (o is ClassB)
   {
      object k = ((ClassB)o).PropertyB;
   }
   if (o is ClassA))
   {
      object j = ((ClassA)o).PropertyA;
   }

2
如果您经常这样做,另一个选择是在列表上创建一个扩展方法,以便为您提供正确类型的枚举。例如:
  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;
        }
      }
    }
  }

然后你可以像这样使用它....
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);
  }
}

1
整个前提都没有意义——对于实例a,PropertyB会是什么?
如果您进行手动运行时类型检查(inst为Foo),然后将其转换为具有所需属性的类型,则可以执行此操作。

0
你可能会在泛型和子类方面遇到一些问题(在这种情况下,您应该返回到System.Collections.ArrayList),但是您必须将BaseClass强制转换为您希望使用的子类。如果您使用'as'目录,则如果BaseClass可以转换为子类,则成功,否则将为空。它看起来会像这样:
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;
    }
}

另外,我应该提到这有点不好。这是那种表明对象继承结构很差的代码。一般来说,如果您不能说一个 IS A 基类,那么您的设计可能出了问题。但是,希望能有所帮助!


0

您需要在基类中将属性声明为虚拟的,然后在派生类中重写它们。

例如:

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

你需要在基类中实现该属性以返回默认值(例如 null),或将其定义为抽象属性并强制所有派生类都实现这两个属性。

你还应该注意,通过在两个派生类中重写 PropertyA 并返回不同的值,可以返回不同的内容。


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