【问题标题】:Creating a read only generic List from a derived class从派生类创建只读泛型列表
【发布时间】:2019-03-05 22:01:30
【问题描述】:

我正在为开发人员开发一个库,他们必须创建一个继承自我创建的类的类。

这个基类本质上为开发者管理一个对象数组,但是开发者可以指定他们希望基类管理的这些对象的类型。

所以开发人员基本上只是告诉基类创建一个数组,然后只有对该数组的只读访问权限。基类将(取决于应用程序的状态)从数组中添加或删除对象。

我一直在寻找合适的数据类型来存储这样的东西。我试过refout 但这让我无处可去。我得到的最接近的是Dictionary,但这个想法失败了,因为 C# 实际上只是将值复制到字典中,而不是引用或指向它。

这是我拼凑的一个简单示例:

    public static void Main()
    {
        Derived d = new Derived();
        d.InitBase();
        d.Init();
        d.CheckArray();
        d.AddElement<GenericObject>(new GenericObject{ i = 2 });
        d.CheckArray();
    }

    public class Base {
        Dictionary<Type, List<object>> ArrayReferences;

        public void InitBase() {
             ArrayReferences = new Dictionary<Type, List<object>>();
        }

        protected ReadOnlyCollection<T> RegisterArray<T>() {
            List<object> managedArray = new List<object>();
            ArrayReferences.Add(typeof(T), managedArray);
            return Array.AsReadOnly(managedArray.Select(s => (T)s).ToArray());
        }

        public void AddElement<T>(T obj) {
            ArrayReferences[typeof(T)].Add(obj);
        }

        public void RemoveElement<T>(T obj) {
            ArrayReferences[typeof(T)].Remove(obj);
        }
    }

    public class Derived: Base {
        ReadOnlyCollection<GenericObject> arr;
        public void Init() {
             arr = RegisterArray<GenericObject>();
        }
        public void CheckArray() {
            Console.WriteLine(arr.Count());
        }
    }

    public class GenericObject {
        public int i = 0;
    }

输出:

0
0

字典显然不会像我想要的那样将值存储为引用。那么 C# 还有哪些其他技术,或者这根本不可能?也不确定unsafe 会引起多少问题,所以我很害怕走那条路。

【问题讨论】:

  • 优先组合而不是继承。与其制作某人为了使用而继承的集合,不如将您的集合密封,并让人们创建它的一个实例以使用该集合。您的派生类不会改变类型的行为,它们只是在使用它。也不要创建方法来初始化对象。该对象应在其构造函数中初始化。
  • 当您在派生类中获得 arr 时,它会在您调用 RegisterArray 的位置创建一个全新的对象数组。您正在向字典中添加对象,但它们没有被添加到该数组中,因为它只是及时的快照。当前(在这种情况下为空)引用集(如果是引用类型)的快照。
  • 问题:为什么需要派生类(而不是封装)?以及您的“派生”(或父母,如果您这样做的话)想如何使用这些快照?
  • @monty 基类将存在于开发人员不应访问的 dll 中。我开发人员创建自己的派生类,他们将在基类中拥有更多与状态相关的代码。
  • @Servy 会试一试。这对我来说有点开箱即用,但如果我理解正确,我认为它会完成这项工作。

标签: c# dictionary generics


【解决方案1】:

虽然我认为有更好的方法来处理这个问题,但这是可以做到的。

存储object,而不是存储List&lt;object&gt; 引用,which isn't compatibleList&lt;T&gt;。在Base 中使用静态来保存Dictionary,因此所有派生类都有一个Dictionary

public static void Main() {
    var d = new Derived();
    d.CheckCollection("d before AddElement");
    d.AddElement(new GenericObject { i = 2 });
    d.CheckCollection("d after AddElement");
    Console.WriteLine($"ListCount = {Base.ListCount}");

    var d2 = new Derived2();
    d2.CheckCollection("d2 before AddElement");
    d2.AddElement(new GenericObject2 { i = 4 });
    d2.AddElement(new GenericObject2 { i = 5 });
    d2.CheckCollection("d2 after AddElement");
    Console.WriteLine($"ListCount = {Base.ListCount}");
}

public class Base {
    static Dictionary<Type, object> ListReferences = new Dictionary<Type, object>();

    public static int ListCount => ListReferences.Count();

    protected ReadOnlyCollection<T> RegisterList<T>() {
        var managedList = new List<T>();
        ListReferences.Add(typeof(T), managedList);
        return managedList.AsReadOnly();
    }

    public void AddElement<T>(T obj) {
        ((List<T>)ListReferences[typeof(T)]).Add(obj);
    }

    public void RemoveElement<T>(T obj) {
        ((List<T>)ListReferences[typeof(T)]).Remove(obj);
    }
}

public class Derived : Base {
    ReadOnlyCollection<GenericObject> roc;
    public Derived() {
        roc = RegisterList<GenericObject>();
    }

    public void CheckCollection(string msg) {
        Console.WriteLine(msg);
        Console.WriteLine(roc.Count());
    }
}

public class Derived2 : Base {
    ReadOnlyCollection<GenericObject2> roc;
    public Derived2() {
        roc = RegisterList<GenericObject2>();
    }

    public void CheckCollection(string msg) {
        Console.WriteLine(msg);
        Console.WriteLine(roc.Count());
    }
}

public class GenericObject {
    public int i = 0;
}

public class GenericObject2 {
    public int i = 0;
}

PS 另外,在使用Lists 时,不要使用“数组”来命名方法和变量。

【讨论】:

    【解决方案2】:

    您编写的以下代码会在您创建列表时复制您的列表 - 因此无论您之后添加什么到列表中,它始终是空的。

    List<object> managedArray = new List<object>();
    ArrayReferences.Add(typeof(T), managedArray);
    return Array.AsReadOnly(managedArray.Select(s => (T)s).ToArray());
    

    以下是您应该如何编写代码以获得您想要的:

    public static void Main()
    {
        Derived d = new Derived();
        Console.WriteLine(d.AsReadOnly().Count);
        d.AddElement(new GenericObject { i = 2 });
        Console.WriteLine(d.AsReadOnly().Count);
    }
    
    public class Base<T>
    {
        List<T> _items = new List<T>();
    
        public ReadOnlyCollection<T> AsReadOnly()
        {
            return Array.AsReadOnly(_items.ToArray());
        }
    
        public void AddElement(T obj)
        {
            _items.Add(obj);
        }
    
        public void RemoveElement(T obj)
        {
            _items.Remove(obj);
        }
    }
    
    public class Derived : Base<GenericObject>
    {
    }
    
    public class GenericObject
    {
        public int i = 0;
    }
    

    输出:

    0 1

    现在,值得考虑的是 List&lt;T&gt; 已经有一个 AsReadOnly() 方法,所以你可以简单地写这个:

    public static void Main()
    {
        var d = new List<GenericObject>();
        Console.WriteLine(d.AsReadOnly().Count);
        d.Add(new GenericObject { i = 2 });
        Console.WriteLine(d.AsReadOnly().Count);
    }
    
    public class GenericObject
    {
        public int i = 0;
    }
    

    这也有效。


    以下是您应该如何执行此操作以一次保存多个列表。不需要继承。

    public static void Main()
    {
        Repository r = new Repository();
        Console.WriteLine(r.AsReadOnly<GenericObject>().Count);
        r.AddElement<GenericObject>(new GenericObject { i = 2 });
        Console.WriteLine(r.AsReadOnly<GenericObject>().Count);
    }
    
    public class Repository
    {
        private Dictionary<Type, object> _references = new Dictionary<Type, object>();
    
        private void Ensure<T>()
        {
            if (!_references.ContainsKey(typeof(T)))
            {
                _references[typeof(T)] = new List<T>();
            }
        }
    
        public ReadOnlyCollection<T> AsReadOnly<T>()
        {
            this.Ensure<T>();
            return (_references[typeof(T)] as List<T>).AsReadOnly();
        }
    
        public void AddElement<T>(T obj)
        {
            this.Ensure<T>();
            (_references[typeof(T)] as List<T>).Add(obj);
        }
    
        public void RemoveElement<T>(T obj)
        {
            this.Ensure<T>();
            (_references[typeof(T)] as List<T>).Remove(obj);
        }
    }
    
    public class GenericObject
    {
        public int i = 0;
    }
    

    【讨论】:

    • 我知道你在这里做什么。但是,基类将管理多个数组,并且它们都将具有自己的泛型类型。因此,我无法按照您的方式使 Base 类通用:public class Base&lt;T&gt;
    • @FanusduToit - 您定义的Base 只能处理一个数组 - 这就是继承的本质。每次继承时,如在class Derived : Base 中,并创建Derived 的新实例,那么您将获得Dictionary&lt;Type, List&lt;object&gt;&gt; ArrayReferences; 的关联实例。如果你想拥有一个Dictionary&lt;Type, List&lt;object&gt;&gt;,那么你必须避免继承,只需要一个Repository 类来替换你的Base 类。
    【解决方案3】:

    在您的基础(或封装类,如果您选择这样做):

        protected ReadOnlyCollection<T> GetSnapshot<T>() {
            return Array.AsReadOnly(ArrayReferences[typeof(T)].Select(s => (T)s).ToArray());
        }
    

    然后您还可以添加任何其他方法来查看数据,例如计数:

        protected int GetCount<T>() {
            return ArrayReferences[typeof(T)].Count;
        }
    

    【讨论】:

    • 这很有趣。我试试看。
    • @FanusduToit - 请记住,这不允许ArrayReferences 保存一种以上类型的列表。你的继承是错误的。
    猜你喜欢
    • 1970-01-01
    • 2014-03-22
    • 2010-10-22
    • 2011-10-09
    • 2010-12-10
    • 2018-12-16
    • 2011-05-24
    • 1970-01-01
    • 2011-02-24
    相关资源
    最近更新 更多