【问题标题】:Interface To Generic Casting issue通用转换问题的接口
【发布时间】:2020-09-25 06:05:38
【问题描述】:

我正在针对内存状态树实现自定义数据存储,但我的索引遇到了一些问题。我的索引是用来覆盖的,所以它们应该返回对象而不仅仅是一个位置。索引具有名称和对象列表。这些对象可以是不同的底层类型,因此索引对象是IHasUUID,这表明项目具有 UUID。

public class DataIndex
{
    public string Name;
    public IDictionary<string, List<IHasUUID>> Index;
}

public class Indexer
{

    private List<DataIndex> Indexes;
...
    public List<IHasUUID> GetIndexedItems(List<IHasUUID> indexBy) 
    {
        var indexer = GetIndexByKeys<IHasUUID>(indexBy);
        var indexHash = GetHashKey(indexBy);

        return GetIndexValues<IHasUUID>(indexer, indexHash);
    }

    private List<T> GetIndexValues<T>(DataIndex indexBy, string indexHash) where T : IHasUUID
    {
        if (indexBy == null)
            return new List<T>();

        return ((IList<T>)indexBy.Index[indexHash]).ToList();
    }

}

我使用反射方法生成字典的键,我查看用作索引键的事物并附加类型字符串名称

所以我让我的引擎去 FindRecords,没问题

public List<T> FindRecords<T>(IHasUUID indexBy) where T : IHasUUID
{
   var indexedIds = Indexer.GetIndexedItems(new List<IHasUUID>() { indexBy });

   return ((IList<T>)indexedIds).ToList();
}

在这里,我在 FindRecords 返回时遇到了一堵墙

我有

return ((IList<T>)indexedIds).ToList();

我试过了

return indexedIds.ToList();

没有一个人能够施展到 T。这可能吗?

提前致谢

编辑

我确实看起来更近了,

    public class DataIndex
    {
        public DataIndex()
        {
            Index = new Dictionary<string, IEnumerable<IHasUUID>>();
        }

        public string Name;
        public Dictionary<string, IEnumerable<IHasUUID>> Index;
    }

    public class Indexer
    {

        private List<DataIndex> Indexes;

        public Indexer()
        {
            Indexes = new List<DataIndex>();
        }
        public IEnumerable<T> GetIndexedItems<T>(IEnumerable<IHasUUID> indexBy) where T : IHasUUID
        {
            var indexer = GetIndexByKeys<T>(indexBy);
            var indexHash = GetHashKey(indexBy);

            return GetIndexValues<T>(indexer, indexHash);
        }
        private IEnumerable<T> GetIndexValues<T>(DataIndex dataIndex, string indexHash) where T : IHasUUID
        {
            if (dataIndex == null)
                return new List<T>();

            return dataIndex.Index[indexHash].ToList() as List<T>;
        }
}

但是,我从GetIndexValues 得到了空回。我也尝试将它作为 IEnumerable 返回,也为 null

这是我的添加到索引方法

public void AddManyToIndex<T>(IEnumerable<IHasUUID> keys, IEnumerable<IHasUUID> newItems) where T : IHasUUID
{
    var index = GetIndexByKeys<T>(keys) ?? CreateIndex<T>(keys);

    string indexKey = GetHashKey(keys);

    if (!index.Index.ContainsKey(indexKey))
    {
        index.Index[indexKey] = new List<IHasUUID>();
    }

        var list = index.Index[indexKey].ToList();
        list.AddRange(newItems.ToList());
        index.Index[indexKey] = list as IEnumerable<IHasUUID>;
}

【问题讨论】:

  • " 一个索引有一个名称和一个对象列表" - 考虑使用与“索引”不同的术语来描述这一点,因为 C# 中的“索引”专门指使用 []运算符。
  • GetIndexedItems 返回List&lt;IHasUUID&gt;,而不是List&lt;T&gt;,并且List&lt;T&gt; 类型(不是接口IList&lt;T&gt;)不支持泛型类型协方差。
  • return indexedIds.Cast&lt;T&gt;().ToList(); 怎么样?不过,它需要在每个元素上运行 Cast&lt;&gt;()。但是,您如何期望 C# 知道特定的 IHasUUIDT?仅仅因为猫是宠物,并不意味着宠物就是猫。
  • @Dai 是正确的。顺便说一句,您真的需要从这些方法中获取和返回列表吗? IMO,这是糟糕的 API 设计。你可以使用IReadOnlyList&lt;T&gt;IEnumerable&lt;T&gt;
  • @JordanBrooklyn 数组实现IReadOnlyList&lt;T&gt;

标签: c# generics reflection interface


【解决方案1】:

System.Collections.Generic.List&lt;T&gt; 不是covariant。也就是说,给定TU 两种类型,其中UTList&lt;U&gt;不是List&lt;T&gt;

这就是强制转换失败的原因,在您的示例中实现IHasUUIDT 的类型列表不是List&lt;IHasUUID&gt;

但也有协变的1 泛型类型,例如System.Collections.Generic.IEnumerable&lt;T&gt;System.Collections.Generic.IReadOnlyList&lt;T&gt;。对于此类类型,给定两种类型 TU,其中 UTIEnumerable&lt;U&gt; IEnumerable&lt;T&gt;

除了解决您的特定问题外,使用此类类型还可以使您的 API 更加灵活,同时使您的实现更简单、更容易。

考虑以下几点:

public interface IHasUuid
{
    Guid Uuid { get; }
}

public class DataIndex
{
    public string Name { get; set; }
    public IDictionary<string, IEnumerable<IHasUuid>> Index { get; } = new Dictionary<string, IEnumerable<IHasUuid>>();
}

public class Indexer
{
    public IEnumerable<IHasUuid> GetIndexedItems(IEnumerable<IHasUuid> indexBy)
    {
        var indexer = GetIndexByKeys<IHasUuid>(indexBy);
        var indexHash = GetHashKey(indexBy);

        return GetIndexValues<IHasUuid>(indexer, indexHash);
    }


    private IEnumerable<T> GetIndexValues<T>(DataIndex dataIndex, string hash) where T : IHasUuid
    {
        if (dataIndex == null)
            return Enumerable.Empty<T>();

        return dataIndex.Index[hash] as IEnumerable<T>;
    }
}

您可以将实现IEnumerable&lt;IHasUuid&gt; 的任何类型存储在DataIndex.Index 中。 .NET 中的所有泛型集合都实现了这个接口,包括List&lt;T&gt;HashSet&lt;T&gt;ConcurrentQueue&lt;T&gt; 等等。

如果您希望在原始代码中保留防御性复制,这可能是明智之举,只需将.ToWhatever() 添加回代码即可。

private IEnumerable<T> GetIndexValues<T>(DataIndex dataIndex, string hash) where T : IHasUuid
{
    if (dataIndex == null)
        return Enumerable.Empty<T>();

    return (dataIndex.Index[hash] as IEnumerable<T>).ToHashSet();
}

例如,您可以像这样构建DataIndex 实例

class Person: IHasUuid {
    public Guid Uuid { get; }
    public string Name { get; }
}

var index = new DataIndex {
    Index = {
        ["People"] = new List<Person>()
    }
};

var indexer = new Indexer();

var people = indexer.GetIndexValues(index, "People");

这是一个有效的小提琴:https://dotnetfiddle.net/qgjXR7

1:如果该类型参数是使用out 修饰符声明的,则该类型在其类型参数上是协变的。顾名思义,out 修饰符意味着它所归属的类型参数只能用于声明类型的输出位置。

interface Wrapper<out T>
{
    T Value { get; } // OK

    T Value { get; set; } // Error

    void SetValue(T value); // Error
}

Interfacedelegate 类型可以声明协变类型参数,类和结构等具体类型不能。


【讨论】:

  • 啊,谢谢你这么详细的解释!我似乎无法真正从 GetIndexValuesdataIndex.Index[indexHash] as IEnumerable&lt;T&gt;; 返回我的数据为空。这些值在转换之前位于索引中。我尝试将它投射到我的实际列表中,仍然为空 return dataIndex.Index[indexHash].ToList() as List&lt;T&gt;; 唯一有效的是 var data = dataIndex.Index[indexHash] as List&lt;IHasUUID&gt;; return data.Cast&lt;T&gt;().ToList();(非常慢)我错过了什么吗?
  • 我在字典中存储了一个实际列表(正如你所建议的那样,string, IEnumerable&lt;IHasUUID&gt;if (!index.Index.ContainsKey(indexKey)) { index.Index[indexKey] = new List&lt;IHasUUID&gt;(); }
  • 我很笨,最后一个例子应该是return (dataIndex.Index[hash] as IEnumerable&lt;T&gt;).ToHashSet();,否则你会得到一个HashSet`。固定示例
  • 啊,是的,我也没听懂。非常感谢,现在一切正常,在这个过程中我学到了很多东西!
  • 我很高兴听到这个消息。那是我的希望。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-26
  • 1970-01-01
  • 1970-01-01
  • 2013-06-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多