C# 内联哈希集<&燃气轮机;声明vs静态属性.Contains()性能

C# 内联哈希集<&燃气轮机;声明vs静态属性.Contains()性能,c#,c#-4.0,hashset,C#,C# 4.0,Hashset,为了使用Contains方法,更好的方法是(有任何区别),使用HashSet声明静态字段或内联声明它(新HashSet{SomeEnum.SomeValue1,SomeEnum.SomeValue2,…}.Contains(SomeEnum.SomeValue1)) 我这样问是因为在某些情况下,我只打算使用hashset一次,对我来说,最好将它放在代码中,而不是放在某个静态属性中 内联示例(我想使用的内容): 如果不打算添加新项,那么静态方法就是正确的方法 private static Hash

为了使用Contains方法,更好的方法是(有任何区别),使用HashSet声明静态字段或内联声明它(新HashSet{SomeEnum.SomeValue1,SomeEnum.SomeValue2,…}.Contains(SomeEnum.SomeValue1))

我这样问是因为在某些情况下,我只打算使用hashset一次,对我来说,最好将它放在代码中,而不是放在某个静态属性中

内联示例(我想使用的内容):


如果不打算添加新项,那么静态方法就是正确的方法

private static HashSet<Type> _values = new HashSet<Type> { Type.TYPE_1, Type.TYPE_2, Type.TYPE_3, Type.TYPE_4 };
public void Validate(Type type) {
    if(!_values.Contains(type)) {
        //do something
    }
}
private static HashSet_values=new HashSet{Type.Type_1,Type.Type_2,Type.Type_3,Type.Type_4};
公共无效验证(类型){
如果(!\u值.包含(类型)){
//做点什么
}
}
System.collections
命名空间中的所有集合都是线程安全的,因此这是一种完全可以接受的方法

如果您以“首选”的方式执行,它仍然可以工作,但是每次调用该函数时您都会重新创建集合,这是一种不必要的开销,肯定会影响性能。

请记住查找的“不频繁”或“一次”用法,正如文章所暗示的那样

如果只在单个方法中使用查找,那么我将使用内联方法,但是我将使用数组(实际上我经常这样做)。我可能会也可能不会使用中间(局部)变量,这取决于我是否认为它使代码更清晰或将来更容易适应

我不会使用静态(甚至实例)变量,因为:

  • 在这种情况下,序列不共享
  • 创建对象(尤其是数组)所需的资源量非常小
  • GC非常适合处理短期对象。。而且,更重要的是,没有必要延长寿命
  • 如果我想在多个方法中共享这个查找,我会考虑创建一个返回新对象的getter(在满足新需求的同时包含上面的#2和#3)。对于局部变量,每个方法每次调用只创建一个新的查找

    我通常使用数组,因为:

  • 语法稍微简单一些(类型可以推断和省略)
  • 数组(尤其是结构)比哈希集更轻量级,但提供了所需的查找功能
  • 搜索一个小数组可能足够快(因为有一个非常小的n)
  • 如果它使特定的代码更难遵循,我不会使用各种各样的长手表格。任务是“包含”,而不是一些更复杂的条件逻辑


    除非存在实际的性能问题,否则请以简洁明了的方式进行,然后继续执行更有趣的任务。

    如果您没有通过性能测试将其视为瓶颈,那么“正确”的方法就是使用对阅读它的人最有意义的代码。这有点主观,所以可能没有“正确”的方法,但任何不容易理解的方法都是“错误”的方法

    我可能只会使用一个内联声明数组,除非值列表在其他方法中是可重用的,或者它太长,以至于无法读取该方法试图执行的操作

    public void Validate(Type type) {
        if(!new[] { Type.TYPE_1, Type.TYPE_2, Type.TYPE_3, Type.TYPE_4 }.Contains(type)) {
            //do something
        }
    }
    
    如果您已经确定这是一个明确的性能瓶颈(意味着您可能每秒要执行数百万次此检查),那么您可能需要使用几种不同的方法进行性能测试,因为正确的答案取决于您试图匹配的集合中有多少项

    除了您建议的方法之外,这里还有一些其他可能更快的方法(但是,您需要再次测试它们以确保:

    标志枚举 看起来您正在使用枚举值。如果该枚举类型具有少量潜在值,则可以将其设置为标志枚举,然后使用位逻辑在单个CPU操作中确定给定值是否与您要查找的任何值匹配

    [Flags]
    public enum Type
    {
        TYPE_1 = 1,
        TYPE_2 = 1<<1,
        TYPE_3 = 1<<2,
        TYPE_4 = 1<<3,
        TYPE_5 = 1<<4,
        // etc...
    }
    
    开关语句 编译器非常擅长找出最快的方法,因此,如果使用switch语句,它可以根据要检查的项的数量和值,决定是否将其编译为一系列if/else检查、HashSet样式方法或跳转表

    switch(type)
    {
        case Type.TYPE_1:
        case Type.TYPE_2:
        case Type.TYPE_3:
        case Type.TYPE_4:
            break;
        default:
            // do something
            break;
    }
    

    在整个过程中一次,或者每次调用一次?如果您在这里给出一个示例,这将非常有帮助。请给出您的代码示例。这太模糊了。添加本地
    哈希集
    可能会影响性能,但添加静态属性更糟糕:它会扼杀可重新进入性。@dasblinkenlight听起来好像它只是为了一个(未修改的)查找。我添加了一些示例,谢谢。如果您非常频繁地调用函数,即使使用少量元素,内联数组也可能比哈希集慢得多。我从
    char[]切换中学到了这一点
    将4个元素添加到一个
    哈希集
    将我的文件处理时间从35秒更改为4秒。@ScottChamberlain确实如此-应该对关键代码执行性能基准测试。这个答案是用“不频繁”或“一次”写成的记住用法。如果我针对特定场景进行优化,可能会完全不同。@ScottChamberlain:这是一个有趣的观察结果。部分原因是数组上的
    .Contains
    是一个LINQ方法,它会做一些额外的反射,看看它是否可以在
    ICollection
    界面上调用一个更优化的方法。如果只需将数组强制转换为
    IList
    ,性能会有相当大的提高。但它仍然比
    HashSet
    慢,至少对于
    char
    值来说是这样。这两种方法都不如一系列简单的
    (c='a'| | c='b'| |…
    检查。逻辑表达式的方式如何?我的问题是有很长的类和方法,我想
    public void Validate(Type type) {
        if(!new[] { Type.TYPE_1, Type.TYPE_2, Type.TYPE_3, Type.TYPE_4 }.Contains(type)) {
            //do something
        }
    }
    
    [Flags]
    public enum Type
    {
        TYPE_1 = 1,
        TYPE_2 = 1<<1,
        TYPE_3 = 1<<2,
        TYPE_4 = 1<<3,
        TYPE_5 = 1<<4,
        // etc...
    }
    
    const Type toMatch = (Type.TYPE_1 | Type.TYPE_2 | Type.TYPE_3 | Type.TYPE_4);
    if((type & toMatch) == 0)
    {
        // do something
    }
    
    switch(type)
    {
        case Type.TYPE_1:
        case Type.TYPE_2:
        case Type.TYPE_3:
        case Type.TYPE_4:
            break;
        default:
            // do something
            break;
    }