没有装箱的System.Enum字典的比较

时间:2017-01-06 22:00:50

标签: c# unity3d garbage-collection

当我尝试创建字典时,我遇到问题,其中键是System.Enum。问题是,像这样的字典会分配垃圾,因为默认的EqualityComparer不是最好的之一。我试着写自己的比较器,但没有任何成功。这有可能吗?

    public enum MyEnum
{
    One, Two, Three
}

public Dictionary<Enum, string> dict = new Dictionary<Enum, string>();

public void Test()
{
    this.dict.Add(MyEnum.One, "One");
    this.dict.Add(MyEnum.Two, "Two");
    this.dict.Add(MyEnum.Three, "Three");

    string result;
    this.dict.TryGetValue(MyEnum.Two, out result); // <-- memory alocation :-(
}

3 个答案:

答案 0 :(得分:2)

在评论中查看您的示例,并且这是针对Unity的,请观看this clip from Unite 2016。它讨论了使用Scriptable Objects而不是字典键的枚举。

你要做的是

public class Program
{
    protected Dictionary<ScriptableObject, string> dict = new Dictionary<ScriptableObject, string>();
}

public class ProgramChild1 : Program
{
    public void Test()
    {
        dict.Add(MyEnum1.One.Instance, "One");
        dict.Add(MyEnum1.Two.Instance, "Two");
        dict.Add(MyEnum1.Three.Instance, "Three");
        string result;
        dict.TryGetValue(MyEnum1.Two.Instance, out result);
    }   
}

public class ProgramChild2 : Program
{

    public void Test()
    {
        dict.Add(MyEnum2.Four.Instance, "One");
        dict.Add(MyEnum2.Five.Instance, "Two");
        dict.Add(MyEnum2.Six.Instance, "Three");
        string result;
        dict.TryGetValue(MyEnum2.Five.Instance, out result);
    }
}

//Each class goes in to its own .cs file, Put them in two folders `MyEnum1` and `MyEnum2`
namespace MyEnum1
{
    public class One : ScriptableObject
    {
        private static One _inst;
        public static One Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<One>();
                if (!_inst)
                    _inst = CreateInstance<One>();
                return _inst;
            }
        }
    }
}  

namespace MyEnum1
{
    public class Two : ScriptableObject
    {
        private static Two _inst;
        public static Two Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<Two>();
                if (!_inst)
                    _inst = CreateInstance<Two>();
                return _inst;
            }
        }
    }
}

namespace MyEnum1
{    
    public class Three : ScriptableObject
    {
        private static Three _inst;
        public static Three Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<Three>();
                if (!_inst)
                    _inst = CreateInstance<Three>();
                return _inst;
            }
        }
    }
}

namespace MyEnum2
{    
    public class Four : ScriptableObject
    {
        private static Four_inst;
        public static Four Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<Four>();
                if (!_inst)
                    _inst = CreateInstance<Four>();
                return _inst;
            }
        }
    }
}

namespace MyEnum2
{    
    public class Five : ScriptableObject
    {
        private static Five _inst;
        public static Five Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<Five>();
                if (!_inst)
                    _inst = CreateInstance<Five>();
                return _inst;
            }
        }
    }
}

namespace MyEnum2
{    
    public class Six : ScriptableObject
    {
        private static Six _inst;
        public static Six Instance
        {
            get
            {
                if (!_inst)
                    _inst = Resources.FindObjectOfType<Six>();
                if (!_inst)
                    _inst = CreateInstance<Six>();
                return _inst;
            }
        }
    }
}

请注意,我们从ScriptableObject继承的原因是,如果您想向设计器公开枚举,则可以将枚举值拖放到设计器中,如果枚举只是在哪里,则无法执行此操作基础课。

public class ProgramChild2 : Program
{
    public ScriptableObject SelectedValue;
    public void Test()
    {
        dict.Add(MyEnum2.Four.Instance, "One");
        dict.Add(MyEnum2.Five.Instance, "Two");
        dict.Add(MyEnum2.Six.Instance, "Three");
        string result;
        dict.TryGetValue(SelectedValue, out result);
    }
}

答案 1 :(得分:0)

您应该编写一个实现IEqualityComparer接口的类。

class MyEnumComparer : IEqualityComparer<MyEnum> { ... }

这意味着您需要同时实现 IEqualityComparer 接口定义的 bool Equals(MyEnum,MyEnum) int GetHashCode(MyEnum)函数

最后,当您创建Dictionary时,请使用构造函数重载来接收 IEqualityComparer 实例,如下所示:

static MyEnumComparer myEnumComparer = new MyEnumComparer();
...
Dictionary<MyEnum, string> dict = new Dictionary<MyEnum, string>(myEnumComparer);

答案 2 :(得分:0)

没有装箱,没有堆分配。非常快。无需为每个枚举编写特定的比较器。

此版本可在任何枚举上运行,只要其基础类型不超过32位(因此byte,ushort,uint都可以)。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public sealed class EnumComparer<T> : IEqualityComparer<T>
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Transformer
    {
        [FieldOffset(0)]
        public T t;

        [FieldOffset(0)]
        public int int32;
    }

    public static EnumComparer<T> Default { get; } = new EnumComparer<T>();

    private EnumComparer()
    {
    }

    public bool Equals(T a, T b)
    {
        Transformer aTransformer = new Transformer { t = a };
        Transformer bTransformer = new Transformer { t = b };
        return aTransformer.int32 == bTransformer.int32;
    }

    public int GetHashCode(T value)
    {
        Transformer valueTransformer = new Transformer { t = value };
        return valueTransformer.int32.GetHashCode();
    }
}

如果要与数组一起使用,则可能需要使用一些扩展方法,然后可以像这样使用它:

bool contained = enumArray.Contains(MyEnum.someValue, EnumComparer<MyEnum>.Default);


public static class Extensions
{
    public static bool Contains<T>(this T[] array, T value, IEqualityComparer<T> equalityComparer)
    {
        if (array == null)
        {
            throw new ArgumentNullException("array");
        }
        return Extensions.IndexOf<T>(array, value, 0, array.Length, equalityComparer) >= 0;
    }

    public static int IndexOf<T>(this T[] array, T value, IEqualityComparer<T> equalityComparer)
    {
        if (array == null)
        {
            throw new ArgumentNullException("array");
        }
        return Extensions.IndexOf<T>(array, value, 0, array.Length, equalityComparer);
    }

    public static int IndexOf<T>(this T[] array, T value, int startIndex, int count, IEqualityComparer<T> equalityComparer)
    {
        if (array == null)
        {
            throw new ArgumentNullException("array");
        }
        if (count < 0 || startIndex < array.GetLowerBound(0) || startIndex - 1 > array.GetUpperBound(0) - count)
        {
            throw new ArgumentOutOfRangeException();
        }

        int num = startIndex + count;
        for (int i = startIndex; i < num; i++)
        {
            if (equalityComparer.Equals(array[i], value))
            {
                return i;
            }
        }
        return -1;
    }
}