在 C# 中:如何声明一个类型为键的通用字典,该类型的 IEnumerable<> 作为值?

Posted

技术标签:

【中文标题】在 C# 中:如何声明一个类型为键的通用字典,该类型的 IEnumerable<> 作为值?【英文标题】:In C#: How to declare a generic Dictionary with a type as key and an IEnumerable<> of that type as value? 【发布时间】:2010-05-31 14:56:02 【问题描述】:

我想声明一个字典,存储特定类型的输入IEnumerable,以该确切类型作为键,如下所示:(编辑以遵循 johny g 的评论)

private IDictionary<Type, IEnumerable<T>> _dataOfType where T: BaseClass; //does not compile!

我要存储的具体类都派生自 BaseClass,因此我想将其用作约束。编译器抱怨它需要在成员名称后加一个分号。

如果可行,我希望这将使以后从字典中检索变得简单,例如:

IEnumerable<ConcreteData> concreteData;
_sitesOfType.TryGetValue(typeof(ConcreteType), out concreteData);

如何定义这样的字典?

【问题讨论】:

普通约束类型除外,声明IDictionary&lt;T, IEnumerable&lt;T&gt;&gt; 将键约束为Tinstances,而不是typeof (T)。你想通过typeof(T)T 的实例来索引你的列表吗?如果是前者,则应声明为 IDictionary&lt;Type, IEnumerable&gt; 并在消费时强制转换可枚举的各个成员 是的,johnny g,你是对的,我想使用类型,而不是实例。我编辑那个。 【参考方案1】:

使用 .Net 框架中已有的 System.ComponentModel.Design.ServiceContainer

        ServiceContainer container = new ServiceContainer();

        IList<int> integers = new List<int>();
        IList<string> strings = new List<string>();
        IList<double> doubles = new List<double>();

        container.AddService(typeof(IEnumerable<int>), integers);
        container.AddService(typeof(IEnumerable<string>), strings);
        container.AddService(typeof(IEnumerable<double>), doubles);

【讨论】:

很好。不过,我现在已经实现了 Eamon Nerbonne 提供的解决方案 这是正确的答案,使用框架中已有的内容。【参考方案2】:

您甚至可能不需要字典就能做到这一点 - 但这取决于您的需要。如果每个 appdomain 的每个类型只需要 1 个这样的列表(即“字典”是 static),那么以下模式可以有效并很好地促进类型推断:

interface IBase 

static class Container 
    static class PerType<T> where T : IBase 
        public static IEnumerable<T> list;
    

    public static IEnumerable<T> Get<T>() where T : IBase 
        => PerType<T>.list; 

    public static void Set<T>(IEnumerable<T> newlist) where T : IBase 
        => PerType<T>.list = newlist;

    public static IEnumerable<T> GetByExample<T>(T ignoredExample) where T : IBase 
        => Get<T>(); 

请注意,在采用这种方法之前,您应该仔细考虑编译时类型和运行时类型之间的区别。这个方法可以让你在SomeTypeSomeType的任何基本类型下存储一个运行时类型的IEnumerable&lt;SomeType&gt;变量,包括IBase,既没有运行时错误,也没有编译类型错误- 这可能是一个功能,或者是一个等待发生的错误,所以你可能需要if 来检查。

此外,这种方法忽略了线程;因此,如果您想从多个线程访问此数据结构,您可能需要添加一些锁定。引用读/写是原子的,因此如果您未能锁定,您不会受到损坏,但陈旧数据和竞争条件肯定是可能的。

【讨论】:

+1 表示第一条评论。您对键的约束是正确的。 感谢您的代码。这非常适合我的情况。我现在用这个。【参考方案3】:

您不要将T 限制在私有成员中;您将其限制在类级别。

class Foo<T> where T : BaseClass

    private IDictionary<T, IEnumerable<T>> _dataOfType;

    public Foo(IDictionary<T, IEnumerable<T>> dataOfType)
    
        this._dataOfType = dataOfType;
       
   

【讨论】:

没错,这可能是它无法编译的原因。然而这不是我想要的,我想要一个类型受限的成员,而不是类的类型约束。【参考方案4】:
    您不能约束特定变量。它仅适用于类和方法。老实说,这在变量级别上真的没有任何意义。 您想要的是一个自定义类 - class WeirdDictionary : IDictionary&lt;Type, IEnumerable&gt;,它将重载 Add 方法以采用该类型的类型和 IEnumerable,您可以使用约束来执行此操作,并将 IEnumerable 强制转换为 IEnumerable。同时重载索引器,并将其转换回相关类型。 所有这些转换都是必需的,因为泛型严格要求 IEnumerable&lt;Base&gt;IEnumerable&lt;Derived&gt; 一样好(我相信这称为方差?)

这个解决方案有点笼统,因为重用岩石

由 280Z28 编辑:

起初我把它记下来,因为第 2 点令人困惑并且我误解了它。通过使用IDictionary&lt;Type, IEnumerable&gt; 中方法的显式实现并提供通用替代方案,您可以获得一个非常干净的接口。请注意,您无法创建通用索引器,因此您必须始终使用TryGet&lt;T&gt;(无论如何这是个好主意)。我只包含了IDictionary&lt;&gt; 方法之一的显式实现,以展示如何执行检查。 不要直接从Dictionary&lt;Type, IEnumerable&gt; 派生WeirdDictionary,否则您将失去在基础数据中保证约束的能力。

class WeirdDictionary : IDictionary<Type, IEnumerable>

    private readonly Dictionary<Type, IEnumerable> _data =
        new Dictionary<Type, IEnumerable>();

    public void Add<T>(IEnumerable<T> value)
    
        _data.Add(typeof(T), value);
    

    public bool TryGet<T>(out IEnumerable<T> value)
    
        IEnumerable enumerable;
        if (_data.TryGetValue(typeof(T), out enumerable)
        
            value = (IEnumerable<T>)enumerable;
            return true;
        

        value = null;
        return false;
    

    // use explicit implementation to discourage use of this method since
    // the manual type checking is much slower that the generic version above
    void IDictionary<Type, IEnumerable>.Add(Type key, IEnumerable value)
    
        if (key == null)
            throw new ArgumentNullException("key");
        if (value != null && !typeof(IEnumerable<>).MakeGenericType(key).IsAssignableFrom(value.GetType()))
            throw new ArgumentException(string.Format("'value' does not implement IEnumerable<0>", key));

        _data.Add(key, value);
    

结束 280Z28

【讨论】:

精心设计的答案。然而,连同其他好的答案,事实证明这比我想象的要复杂得多。由于目前我只有 2 个派生类,因此我现在倾向于将两个单独的枚举直接作为我的类中的属性进行管理。 :-( 我对 280Z28 提供的实现进行了一些测试,它在所有类上都运行良好并且运行良好。这是一个巧妙而奇怪的想法。【参考方案5】:

制作一个自定义 Dictionary 类:

public class BaseClassDictionary<T, IEnumerable<T>> : Dictionary<T, IEnumerable<T>>
    where T : BaseClass


然后你可以使用这个专门的字典来代替字段类型:

private BaseClassDictionary<BaseClassDerivedType, IEnumerable<BaseClassDerivedType>> myDictionary;

【讨论】:

@Lucas 我不明白你的意思 @Marcel,在这里回复,但这也适用于我下面的答案......这不会给你多个字典。正如您所指定的,它将为您提供一个字典,其键集合是 T 的集合,其值集合是实现 T 的 IEnumerable 的任何对象的集合,即(可以是 T 的数组, Ts,Ts 的链接列表等。或者这些项目的任何组合...)这可能仍然不是您想要的,但它就是您所说的... @Charles Bretana:你说得对,只需要一本字典。我删除了我的其他评论。【参考方案6】:

试试这个:

public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>>    

【讨论】:

不是我真正需要的,另请参阅我对 johnny g 的评论。 @Marcel,在 Enrico 的回答下方评论

以上是关于在 C# 中:如何声明一个类型为键的通用字典,该类型的 IEnumerable<> 作为值?的主要内容,如果未能解决你的问题,请参考以下文章

JSON序列化以元组为键的字典

Pandas:创建一个以元组为键的字典

在 C# 中将匿名类型转换为键/值数组?

C#如何创建具有多个相同键的字典? [复制]

如何将带有元组键的 python 字典转换为 pandas 多索引数据框?

如何在Android中转换以日期为键的JSON [重复]