代码之家  ›  专栏  ›  技术社区  ›  PMF

高效地从列表中返回i list<interface>(避免从列表中强制转换到列表中)

  •  1
  • PMF  · 技术社区  · 8 年前

    我有以下代码:

    public interface ISomeObject
    {
         IList<ISomeObject> Objects { get; }
    }
    public class SomeObject : ISomeObject
    {
        public SomeObject()
        {
            Objects = new List<SomeObject>();
        }
        public List<SomeObject> Objects
        {
             get;
             set;
        }
        IList<ISomeObject> ISomeObject.Objects
        {    
            get 
            {
                // What to do here?
                // return Objects; // This doesn't work
                return Objects.Cast<ISomeObject>().ToList(); // Works, but creates a copy each time.
             }
        }
    

    SomeObject 拥有公共财产 Objects 返回类类型的列表。知道类类型的客户机可以使用它来做他们想要的任何事情。客户只知道 ISomeObject 可以使用 物体 属性仅用于获取 IList<ISomeObject> 。因为不允许铸造 List<SomeObject> ilist<isomeObject> (由于 apple and banana issue )我需要一种转换的方法。使用cast.tolist()的默认方法是有效的,但缺点是每次评估属性时都会创建一个新的列表,这可能很昂贵。改变 ISomeObject.Objects 返回 IEnumerable<ISomeObject> 另一个缺点是客户机不能再使用索引了(这在我的用例中非常相关)。在IEnumerable上重复使用Linq的elementAt()调用是很昂贵的。

    有人知道如何避免这两个问题吗? (当然,制作 某个对象 无所不知不是一种选择)。

    3 回复  |  直到 8 年前
        1
  •  4
  •   xanatos    8 年前

    您可以/应该实现类似于 ReadOnlyCollection<T> 作为代理人。考虑到它是只读的,它可能是“协变的”(不是语言方面的,而是逻辑上的,这意味着它可以代理 TDest 这是的子类/接口 TSource )然后 throw NotSupportedException() 所有的写入方法。

    类似这样(代码未测试):

    public class CovariantReadOlyList<TSource, TDest> : IList<TDest>, IReadOnlyList<TDest> where TSource : class, TDest
    {
        private readonly IList<TSource> source;
    
        public CovariantReadOlyList(IList<TSource> source)
        {
            this.source = source;
        }
    
        public TDest this[int index] { get => source[index]; set => throw new NotSupportedException(); }
    
        public int Count => source.Count;
    
        public bool IsReadOnly => true;
    
        public void Add(TDest item) => throw new NotSupportedException();
    
        public void Clear() => throw new NotSupportedException();
    
        public bool Contains(TDest item) => IndexOf(item) != -1;
    
        public void CopyTo(TDest[] array, int arrayIndex)
        {
            // Using the nuget package System.Runtime.CompilerServices.Unsafe
            // source.CopyTo(Unsafe.As<TSource[]>(array), arrayIndex);
            // We love to play with fire :-)
    
            foreach (TSource ele in source)
            {
                array[arrayIndex] = ele;
                arrayIndex++;
            }
        }
    
        public IEnumerator<TDest> GetEnumerator() => ((IEnumerable<TDest>)source).GetEnumerator();
    
        public int IndexOf(TDest item)
        {
            TSource item2 = item as TSource;
    
            if (ReferenceEquals(item2, null) && !ReferenceEquals(item, null))
            {
                return -1;
            }
    
            return source.IndexOf(item2);
        }
    
        public void Insert(int index, TDest item)
        {
            throw new NotSupportedException();
        }
    
        public bool Remove(TDest item)
        {
            throw new NotSupportedException();
        }
    
        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    

    使用方法如下:

    IList<string> strs = new List<string>();
    IList<object> objs = new CovariantReadOlyList<string, object>(strs);
    
        2
  •  3
  •   user743382    8 年前

    改变 ISomeObject.Objects 返回 IEnumerable<ISomeObject> 另一个缺点是客户机不能再使用索引了(这在我的用例中非常相关)。

    索引不仅受 IList<T> 接口,它还支持 IReadOnlyList<T> 接口。因为 iReadOnlyList<t> 不允许修改,它可以是协变的,就像 IEnumerable<T> 是。

    所以,只需将返回类型更改为 IReadOnlyList<ISomeObject> 并返回原始列表。

    当然,没有什么可以阻止调用者将结果强制转换为 List<SomeObject> 但无论如何,调用者应该可以完全访问该列表,因此不存在安全风险。

        3
  •  0
  •   UladzislaÅ­    8 年前

    您可能需要尝试封装 List<SomeObject> IReadOnlyList<SomeObject> 相反。然后 SomeObject ISomeObject 在接口实现中不需要强制转换,因为 IReadOnlyList 差异-您可以返回 Objects 作为 IReadOnlyList<ISomeObject> .

    然后添加一些操作来改变您的基础列表,比如 Add Remove 如果需要,则为容器类型。

    另外,我还应该提到,接口对于限制来说并不是那么好-邪恶的消费者可以轻松地 对象 某个对象 做他想做的一切,也许你应该重新考虑你的设计。为了提供可用的API,最好坚持不可变和封装之类的东西。然后,在合理的不可变类中显式地使用可变生成器。