4

有一个非常简单的技巧可以创建一个类似字典的结构,其中键是类型。该结构的作用类似于Dictionary<Type, T?>其中键是Type对象,值是相应类型的实例。

这个奇妙的结构与变量或数组一样快,因为“查找”只由编译器/JITter 完成一次,并且正确的值引用被编译到您的程序中。

    public static class MyDict<T> {
        public static T Value { get; set; }
    }

您可以像这样使用该结构:

MyDict<string>.Value = MyDict<int>.Value.ToString();

问题是这个“字典”是全球性的。创建不同字典的唯一方法是创建不同的类。

如何创建一个类似的(最快的“查找”,没有装箱)非静态结构?(没有代码生成。)

简单地说:我想拥有多个Dictionary<Type, object>类似的对象,而不需要查找成本、铸造和装箱。

4

8 回答 8

2

这是一种扩展问题中描述的方法的方法:

public class TypeDict
{
    public T Get<T>()
    {
        return MyDict<T>.Values[this];
    }
    public void Set<T>(T value)
    {
        MyDict<T>.Values[this] = value;
    }
    private static class MyDict<T>
    {
        public static Dictionary<TypeDict, T> Values { get; private set; }

        static MyDict()
        {
            Values = new Dictionary<TypeDict, T>();
        }
    }
}

现在我们可以像这样使用 TypeDict:

void X()
{
    var a = new TypeDict();
    var b = new TypeDict();

    a.Set<int>(1);
    a.Set<double>(3.14);
    a.Set("Hello, world!");

    //Note that type inference allows us to omit the type argument
    b.Set(10);          
    b.Set(31.4);  
    b.Set("Hello, world, times ten!");

    Console.WriteLine(a.Get<int>());
    Console.WriteLine(a.Get<double>());
    Console.WriteLine(a.Get<string>());

    Console.WriteLine();
    Console.WriteLine(b.Get<int>());
    Console.WriteLine(b.Get<double>());
    Console.WriteLine(b.Get<string>());
}
于 2012-12-28T04:10:34.640 回答
1

Ark-kun 使用泛型本质上在编译时生成唯一类型。对于泛型类型,任何静态成员对于特定的封闭泛型类型都是唯一的。这样,它的处理速度与标准静态成员查找一样快。

上面的用法等价于这样的:

public static class MyDict_String 
{
    public static string Value { get; set; }
}

public static class MyDict_Int32
{
    public static int Value { get; set; }
}

MyDict_String.Value = MyDict_Int32.Value.ToString();

AFAIK,类型是“静态的”(因为你不能以这种方式定义多个)所以我不知道有一种方法可以绕过这个并保持静态编译的成员查找的相同性能。

否则(我认为)你最好的选择是创建一个通用实例类型,它包装自己的字典,System.Type用于其键和System.Object值,在插入/检索值时必须对其执行装箱/转换。

编辑:这是一个包装字典的简单实现:

public class MyTypedDict
{
    private Dictionary<Type, object> Values = new Dictionary<Type, object>();

    public T Get<T>()
    {
        object untypedValue;
        if (Values.TryGetValue(typeof(T), out untypedValue))
            return (T)untypedValue;
        return default(T);
    }

    public void Set<T>(T value)
    {
        Values[typeof(T)] = value;
    }
}

多想一想,有可能通过一些愚蠢的行为使用ExpandoObjecthttp://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx)来实现更像属性的语法,但我感觉这将是非常滥用的,我只能假设非常容易出现运行时错误。(另外它在编译时不会给你任何东西)

EDITx2:如果你真的想要不同的值集,你可以将它嵌套在另一个泛型类型中:

public static class ValueSets<T>
{
    public static class MyDict<U>
    {
        public static U Value { get; set; }
    }
}

使用如下:

ValueSets<int>.MyDict<string>.Value = "Hello ";
ValueSets<bool>.MyDict<string>.Value = "World!";

string helloworld = ValueSets<int>.MyDict<string>.Value + ValueSets<bool>.MyDict<string>.Value;
Console.WriteLine(helloworld);//Hello World!

但是在这种情况下,初始类型变得“神奇”int并且bool没有意义,而且您需要为每个想要使用的不同值集提供唯一类型。另外,您不能将其传递并修改为实例变量,而是可以静态访问(只要您有权使用 type T)。因此,也许您可​​以声明以含义命名的最小可见类型并使用它们:

internal class MyFirstWords {}
internal class MySecondWords {}

ValueSets<MyFirstWords>.MyDict<string>.Value = "Hello ";
ValueSets<MySecondWords>.MyDict<string>.Value = "World!";

string helloworld = ValueSets<MyFirstWords>.MyDict<string>.Value + ValueSets<MySecondWords>.MyDict<string>.Value;
Console.WriteLine(helloworld);//Hello World!

无论如何,我认为这很古怪,我不会推荐它。

于 2012-12-28T02:59:42.723 回答
1

一个更复杂的版本。不知道是不是更近:

定义一个通用字典:

public class MyDictionary<T>
{
    Dictionary<string, T> dict;

    public MyDictionary()
    {
        dict = new Dictionary<string, T>();
    }

    public T this[string name]
    {
        get
        {
            if (dict.ContainsKey(name))
                return dict[name];
            else
                return default(T);//or throw
        }
        set
        {
            dict[name] = value;
        }
    }
}

然后是存储这些字典的存储库:

public class MyRepository
{
    List<object> repo;

    public MyRepository()
    {
        repo = new List<object>();
    }

    public void Add<T>(string name, T value)
    {
        if (!repo.OfType<MyDictionary<T>>().Any())
            repo.Add(new MyDictionary<T>());
        var dict = repo.OfType<MyDictionary<T>>().FirstOrDefault();
        dict[name] = value;
    }

    public T GetValue<T>(string name)
    {
        if (!repo.OfType<MyDictionary<T>>().Any())
            return default(T);//or throw
        else
        {
            var dict = repo.OfType<MyDictionary<T>>().FirstOrDefault();
            return dict[name];
        }
    }
}

最后你可以使用这个存储库:

        MyRepository repo = new MyRepository();
        repo.Add("A", 1);
        repo.Add("B", 1);
        int i = repo.GetValue<int>("A") + repo.GetValue<int>("B");

在此示例中,左侧有MyDictionary<T>拳击。object

另一方面,如果您使用某些特定类型,则可能根本不使用存储库类。但使用单独的词典。

MyDictionary<int> intDict = new MyDictionary<int>();
intDict["A"] = 1;
intDict["B"] = 2;
int i = intDict["A"] + intDict["B"];

但是,它与使用相同

Dictionary<string, int> intDict = new Dictionary<string, int>();

因此MyRepository可以编辑该类以使用Dictionary<string, T>而不是MyDictionary<T>.

于 2012-12-28T03:18:23.097 回答
1

@Konstantin 的回答让我想起实际上有一种非常快速的查找方法——数组索引。这个粗略的 PoC 代码显示了所需结构的变体。

    public class TypeDictionary {
        static int _maxId = 0;
        int _id;

        static class Store<T>{
            internal static List<T> Values = new List<T>();
        }

        public TypeDictionary() {
            _id = _maxId++;
        }

        public T GetValue<T>() {
            return Store<T>.Values[_id];
        }

        public void SetValue<T>(T value) {
            while(Store<T>.Values.Count < _id) {
                Store<T>.Values.Add(default(T));
            }
            Store<T>.Values[_id] = value;
        } 
    }

此代码可按如下方式使用:

        var dict1 = new TypeDictionary();
        dict1.SetValue("my string");
        string result = dict1.GetValue<string>();

这个解决方案的问题是它的内存使用是由存储库不稀疏引起的。这也使得首次值设置更加昂贵。

于 2012-12-28T03:48:14.620 回答
0

试试这个:

public class MyDictionary
{
    List<object> values;

    public MyDictionary()
    {
        values = new List<object>();
    }

    public T GetValue<T>()
    {
        return values.OfType<T>().FirstOrDefault();
    }

    public bool Add<T>(T value)
    {
        if (values.OfType<T>().Any())
            return false;
        else
        {
            values.Add(value);
            return true;
        }
    }
}

并使用它:

var md = new MyDictionary();
md.Add("!!!");
string s = md.GetValue<string>();

此类最多可存储一个 type 值T。但是我猜可能存在派生类和接口的极端情况。您可以检查它是否适合您的需要,并可能根据需要对其进行修改,如果它接近您的总体需要。

于 2012-12-28T03:05:41.430 回答
0

您正在寻找的东西在 C# 中是不可能的。该语言不支持可以存储多个不同类型对象的容器,但提供了一种不涉及强制转换、装箱或拆箱的查找方法。您可以使用 C++ 中的宏或通过 javascript 之类的语言来完成类似的操作,其中类型的结构可以在运行时更改。

于 2013-01-03T06:16:21.137 回答
0

ConditionalWeakTable<TKey,TValue>您描述的用例与添加到 .NET 4.0的目的非常吻合。出于您描述的目的,您将在静态泛型类中包含这样一个表,然后对于应该包含对特定类型的项目的引用的每个类对象,您将在该类型的表中存储对应该的对象的引用包含项目以及对项目的引用,或者对简单项目持有者对象的引用(请注意,ConditionalWeakTable当对象不再存在时,条目将消失,但在其他方面是不可变的,所以如果你想要一个可变关联您需要创建一个对象来保存它)。

于 2013-12-10T17:57:14.713 回答
0

以@phoog 的示例和@supercat 的建议为基础

public class TypeDict
{
    public T Get<T>() where T : class
    {
        T value;
        InnerDict<T>.Values.TryGetValue(this, out value);
        return value;
    }
    public void Set<T>(T value) where T : class
    {
        var cwt = InnerDict<T>.Values;
        // lock+remove+add https://github.com/dotnet/coreclr/issues/4545
        lock (cwt)
        {
            cwt.Remove(this);
            cwt.Add(this, value);
        }
    }
    private static class InnerDict<T> where T : class
    {
        public static ConditionalWeakTable<TypeDict, T> Values { get; private set; }

        static InnerDict()
        {
            Values = new ConditionalWeakTable<TypeDict, T>();
        }
    }
}
于 2016-04-24T17:56:24.843 回答