【问题标题】:Creating a constant Dictionary in C#在 C# 中创建常量字典
【发布时间】:2010-09-21 01:13:56
【问题描述】:

创建strings 到ints 的常量(在运行时从不更改)映射的最有效方法是什么?

我尝试使用const Dictionary,但没有成功。

我可以用适当的语义实现immutable wrapper,但这似乎仍然不完全正确。


对于那些问过的人,我正在生成的类中实现IDataErrorInfo,并正在寻找一种方法来将 columnName 查找到我的描述符数组中。

我不知道(测试时打错字!d'oh!)那个开关接受字符串,所以这就是我要使用的。谢谢!

【问题讨论】:

标签: c# .net collections dictionary constants


【解决方案1】:

字典似乎没有任何标准的不可变接口,因此不幸的是,创建包装器似乎是唯一合理的选择。

编辑:Marc Gravell 找到了我错过的 ILookup - 这至少可以让您避免创建新的包装器,尽管您仍然需要使用 .ToLookup() 转换 Dictionary。

如果这是一种受限于特定场景的需求,您最好使用更面向业务逻辑的界面:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}

【讨论】:

    【解决方案2】:

    当前框架中的不可变集合很少。我可以想到 .NET 3.5 中一种相对无痛的选择:

    使用Enumerable.ToLookup() - Lookup<,> 类是不可变的(但在右轴上是多值的);你可以很容易地通过Dictionary<,> 做到这一点:

        Dictionary<string, int> ids = new Dictionary<string, int> {
          {"abc",1}, {"def",2}, {"ghi",3}
        };
        ILookup<string, int> lookup = ids.ToLookup(x => x.Key, x => x.Value);
        int i = lookup["def"].Single();
    

    【讨论】:

      【解决方案3】:
      enum Constants
      {
          Abc = 1,
          Def = 2,
          Ghi = 3
      }
      
      ...
      
      int i = (int)Enum.Parse(typeof(Constants), "Def");
      

      【讨论】:

      • 有趣的想法!我想知道 Parse() 调用的性能如何。我担心只有剖析师才能回答这个问题。
      【解决方案4】:

      在 C# 中创建真正的编译时生成的常量字典并不是一项简单的任务。实际上,这里的答案都没有真正实现这一点。

      有一种解决方案可以满足您的要求,但不一定是好的解决方案;请记住,根据 C# 规范,switch-case 表被编译为常量散列跳转表。也就是说,它们是常量字典,而不是一系列 if-else 语句。所以考虑这样的 switch-case 语句:

      switch (myString)
      {
         case "cat": return 0;
         case "dog": return 1;
         case "elephant": return 3;
      }
      

      这正是您想要的。是的,我知道,这很丑。

      【讨论】:

      • 无论如何它都是生成的代码,具有良好的性能特征,可以在编译时计算,并且对于我的用例也有其他不错的属性。我会这样做的!
      • 请注意返回非值类型,即使是这样,也会破坏您的类的不变性。
      • switch-case 非常棒,直到你需要通过值“foreach”。
      • 它缺少很多Dictionaries的好功能。
      • @TomAnderson:如果返回的项目是值类型(可变或不可变),或者它们是不可变的类对象,则该构造将表现为不可变。
      【解决方案5】:

      这是最接近“CONST 字典”的东西:

      public static int GetValueByName(string name)
      {
          switch (name)
          {
              case "bob": return 1;
              case "billy": return 2;
              default: return -1;
          }
      }
      

      编译器将足够聪明,以尽可能干净地构建代码。

      【讨论】:

        【解决方案6】:

        为什么不:

        public class MyClass
        {
            private Dictionary<string, int> _myCollection = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };
        
            public IEnumerable<KeyValuePair<string,int>> MyCollection
            {
                get { return _myCollection.AsEnumerable<KeyValuePair<string, int>>(); }
            }
        }

        【讨论】:

        • 因为与指定条件下的可用替代品相比,这相当昂贵。
        • 也因为这甚至不编译
        • @AustinWBryan 是的,它可以编译
        【解决方案7】:

        为什么不使用命名空间或类来嵌套你的值呢?它可能不完美,但很干净。

        public static class ParentClass
        {
            // here is the "dictionary" class
            public static class FooDictionary
            {
                public const string Key1 = "somevalue";
                public const string Foobar = "fubar";
            }
        }
        

        现在您可以访问 .ParentClass.FooDictionary.Key1 等。

        【讨论】:

        • 因为这里没有实现IDataErrorInfo接口。
        【解决方案8】:

        如果使用 4.5+ 框架,我将使用 ReadOnlyDictionary(也用于列表的 ReadOnly Collection)来执行只读映射/常量。它的实现方式如下。

        static class SomeClass
        {
            static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
                = new ReadOnlyDictionary<string,int>(
                    new Dictionary<string,int>()
                    {
                        { "One", 1 },
                        { "Two", 2 }
                    }
                )
        }        
        

        【讨论】:

        • private static readonly Dictionary&lt;string, string&gt; _yourDictionaryName = new Dictionary&lt;string, int&gt;(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, }; 我就是这样做的。
        • @SanketSonavane readonly Dictionary&lt;TKey, TValue&gt; 不等同于 ReadOnlyDictionary&lt;TKey, TValue&gt;。实际上,它们“只读”的方式彼此完全相反。请参阅:dotnetfiddle.net/v0l4aA
        【解决方案9】:

        我不确定为什么没有人提到这一点,但在 C# 中对于我无法分配 const 的东西,我使用静态只读属性。

        例子:

        public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
                {
                    { "Reference1", Array1 },
                    { "Reference2", Array2 },
                    { "Reference3", Array3 },
                    { "Reference4", Array4 },
                    { "Reference5", Array5 }
                };
        

        【讨论】:

        • 因为字典的内容还是可变的,而且是在运行时分配的。
        【解决方案10】:

        只是另一个想法,因为我绑定到一个 winforms 组合框:

        public enum DateRange {
            [Display(Name = "None")]
            None = 0,
            [Display(Name = "Today")]
            Today = 1,
            [Display(Name = "Tomorrow")]
            Tomorrow = 2,
            [Display(Name = "Yesterday")]
            Yesterday = 3,
            [Display(Name = "Last 7 Days")]
            LastSeven = 4,
            [Display(Name = "Custom")]
            Custom = 99
            };
        
        int something = (int)DateRange.None;
        

        从显示名称from中获取int值:

        public static class EnumHelper<T>
        {
            public static T GetValueFromName(string name)
            {
                var type = typeof(T);
                if (!type.IsEnum) throw new InvalidOperationException();
        
                foreach (var field in type.GetFields())
                {
                    var attribute = Attribute.GetCustomAttribute(field,
                        typeof(DisplayAttribute)) as DisplayAttribute;
                    if (attribute != null)
                    {
                        if (attribute.Name == name)
                        {
                            return (T)field.GetValue(null);
                        }
                    }
                    else
                    {
                        if (field.Name == name)
                            return (T)field.GetValue(null);
                    }
                }
        
                throw new ArgumentOutOfRangeException("name");
            }
        }
        

        用法:

        var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");
        

        【讨论】:

          【解决方案11】:

          从 C# 8 开始,new switch expression 是实现结果的最简洁方式:

          int value = inputString switch {
              "one" => 1,
              "two" => 2,
              _ => -1
          };
          

          或作为函数

          int GetValue(string inputString) => inputString switch {
              "one" => 1,
              "two" => 2,
              _ => -1
          };
          

          “旧”的更简洁:

          int value = -1;
          switch (inputString){
              case "one": value=1; break;
              case "two": value=2; break;
          }
          

          新旧编译器的性能差异可能因编译器而异

          【讨论】:

          • 这当然更简洁,但你能解释/展示这比常规 switch 语句快多少(在 CPU 时间上)?我会假设(!)一个好的(!)编译器可以为您的两个示例创建完全相同的 CLR 指令。
          • @DavidSchmitt 你是对的,我已经得出了关于使用 Codewars 的默认编译器的速度的结论,当我在 Linqpad 中重新运行相同的东西时,没有显着差异
          猜你喜欢
          • 2011-04-20
          • 2010-10-25
          • 1970-01-01
          • 2016-12-05
          • 1970-01-01
          • 2020-07-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多