【问题标题】:Can a generic collection prevent the adding of derived objects泛型集合可以防止添加派生对象吗
【发布时间】:2014-09-18 14:37:25
【问题描述】:

给定一个类和一个子类

public class Base
{
  public string Name { get; set; }
}

public class Derived : Base
{
  public string Extra {get; set; }
}

还有一个通用列表

var list = new List<Base>();

我想防止这种情况发生:

var b = new Base { Name = "base" };
var d = new Derived { Name = "derived", Extra = "stuff" };

list.Add(b); // this is good
list.Add(d); // this is BAD

想要阻止这种情况的原因是列表将以丢失类型信息的方式进行序列化,然后重新序列化为List&lt;Base&gt;。从Base 派生的任何项目都需要向下转换为反序列化程序未知的类型(我当然不希望使用反射来查找从 Base 继承并具有“额外”属性的类)。也许我会努力解决这个问题,这可能会导致另一个问题。但是现在,我可以通过阻止派生对象添加到通用列表来避免这个问题吗?

【问题讨论】:

  • 它们可能有用...如何将不同的对象放入列表中并对其进行序列化/反序列化并保留所有类型信息:weblogs.asp.net/stevewellens/…
  • @Mephy:如果我了解 Liskov 在这里的应用方式,也许答案是向上转换在序列化之前添加的任何派生对象。在此示例中,这将删除 Extra。然后反序列化只会看到一个统一的列表。这很有意义。

标签: c# generics serialization


【解决方案1】:
public class UberStrongTypedList<T> : IList<T>
{
    private readonly IList<T> _list = new List<T>();

    public void Add(T item)
    {
        if (item.GetType() == typeof (T))
            _list.Add(item);
        else
            ;//some error
    }

    public void Clear()
    {
        _list.Clear();
    }

    //SNIP...
}

【讨论】:

    【解决方案2】:

    不,不能使用 C# 类型系统。继承的整个前提是派生对象可以被视为任何基类型(或接口)的实例。

    您可以创建一个新集合,当尝试添加项目时,它会执行运行时检查以验证该类型是否与集合泛型类型精确匹配,但这是最好的你可以做。您将无法获得此约束的静态类型验证。

    【讨论】:

    • 我接受了这个答案,因为虽然我的问题并不清楚,但我想知道是否有一种声明性的方式来完成这个。感谢那些建议自定义列表/检查 Add() 方法的人(感谢 @user2711965 是第一个,感谢 ''decPL'' 提供示例)。这可能是我会做的一些版本,但正如“Servy”在另一条评论中指出的那样,简单地将 List 子类化并不会阻止访问。封装(与 decPL 一样)将容器固定为单一类型(好吧,除非我也将容器通用化......嗯)。
    【解决方案3】:

    为什么不做一个简单的检查:

    if(!obj is Derived)
    {
       list.Add(obj);
    }
    

    如果你想与基类进行比较,那么你可以这样做:

    if(obj != null && obj.GetType() == typeof(Base))
    {
        list.Add(obj);
    }
    

    记住,这些检查无法阻止没有这些检查的代码将子类对象添加到列表中。您的另一个选择是创建您的自定义 List&lt;T&gt; 类,派生自 List&lt;T&gt; 并提供 new Add 方法,如 this answer. 中所述

    【讨论】:

    • 直截了当的解决方案。但是如果 OP 有很多从基类派生的类或者将来会创建这样的类怎么办?
    • @AndyKorneyev,这是一个有效的观点。我还根据基类的精确比较更新了我的答案。
    • 这些解决方案都不能阻止某人将派生类型添加到List。其他一些代码仍然可以将派生类型添加到列表中。
    • @Servy,当然,如果没有确切的类型检查,或者没有使用 new Add 方法的自定义列表,您将无法阻止这种情况。
    • @user2711965 隐藏Add 方法不会阻止对该方法的访问,它只需要将类型转换为基本类型/接口。是的,有种方法可以实际上阻止添加派生类型,即封装集合而不是继承它,这使您能够准确地提供以下行为:是理想的,不留漏洞。
    【解决方案4】:

    子类列表:

    private class Parent
    {
        public int ID { get; set; }
    }
    
    private class Child : Parent
    {
    
    }
    
    private class MyList : List<Parent>
    {
        public new void Add(Parent item)
        {
            //runtime type check in case a Parent-derived object reaches here
            if (item.GetType() == typeof(Parent))
                base.Add(item);
            else
                throw new ArgumentException("List only supports Parent types");
        }
    
        [Obsolete("You can't add Child types to this list", true)]
        public void Add(Child item)
        {
            throw new NotImplementedException();
        }
    }
    
    static void Main(string[] args)
    {
        var list = new MyList();
        list.Add(new Parent() { ID = 1 });
        list.Add(new Child() { ID = 2 }); //This gives a compiler error
    }
    

    请注意,带有 true 参数的 Obsolete 属性是导致编译器错误的原因。

    【讨论】:

    • 现在添加一个新类Child2 : Parentlist.Add(new Child2() { ID = 3}); 将正确编译。
    • 这仍然启用list.Add((Parent)new Child() { ID = 2 });((List&lt;Parent&gt;)list).Add(new Child() { ID = 2 });
    • 确实如此。如果 OP 对代码有严格的控制并且可以确保没有 Child2,那么 OP 将从我的解决方案中受益,因为它会给出编译时错误。我可以更新我的答案以包括类型检查。
    猜你喜欢
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-15
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多