【问题标题】:Initialization of static fields in C# generic typesC# 泛型类型中静态字段的初始化
【发布时间】:2011-03-01 04:09:45
【问题描述】:

我从this answer 了解到,C# 静态字段初始值设定项“在第一次使用该类的静态字段之前执行……”但这仍然会产生我没想到的结果,至少对于泛型类型.

来自 Java 世界,我想念我丰富的枚举,我认为使用 C# 的更重要的泛型,我应该能够用最少的样板来复制它们。这里(去掉了一些细节,比如可比性)是我想出的:

public class AbstractEnum<T> where T : AbstractEnum<T>
{
    static readonly IDictionary<String, T> nameRegistry = new Dictionary<String, T>();

    readonly String name;

    protected AbstractEnum (String name)
    {
        this.name = name;
        nameRegistry[name] = (T) this;
    }

    public String Name {
        get {
            return name;
        }
    }

    public static T ValueOf(String name) {
        return nameRegistry[name];
    }

    public static IEnumerable<T> Values {
        get {
            return nameRegistry.Values;
        }
    }
}

还有一些示例子类:

public class SomeEnum : AbstractEnum<SomeEnum> {

    public static readonly SomeEnum V1 = new SomeEnum("V1");
    public static readonly SomeEnum V2 = new SomeEnum("V2");

    SomeEnum(String name) : base(name) {

    }
}

public class OtherEnum : AbstractEnum<OtherEnum> {

    public static readonly OtherEnum V1 = new OtherEnum("V1");
    public static readonly OtherEnum V2 = new OtherEnum("V2");

    OtherEnum(String name) : base(name) {

    }
}

这看起来不错,而且或多或少有诀窍...除了按照规范的字母,实际实例(SomeEnum.V1OtherEnum.V1 等)不会被初始化,除非至少有一个它们被明确提及。基类中的静态字段/方法不计算在内。因此,例如,以下内容:

Console.WriteLine("Count: {0}", SomeEnum.Values.Count());
foreach (SomeEnum e in SomeEnum.Values) {
    Console.WriteLine(e.Name);
}

Count: 0,但如果我添加以下行--

Console.WriteLine("SomeEnum.V1: " + SomeEnum.V1.Name);

-- 甚至 以上,我得到:

Count: 2
V1
V2

(顺便提一下,在静态构造函数中初始化实例没有区别。)

现在,我可以通过将nameRegistry 标记为protected 并将ValuesValueOf 推入子类来解决此问题,但我希望保持超类中的所有复杂性并将样板保持在最低限度。任何 C#-fu 优于我的人都可以想出一个使子类实例“自动执行”的技巧吗?


注意:FWIW,这是 Mac OS 上的 Mono。 YM 在 MS .NET 上,在 Windows 上,MV。


预计到达时间: 对于单语 C# 开发人员(甚至是经验仅限于以“C”开头的语言的多语言开发人员)想知道我正在尝试做的 WTF:this。 C# 枚举负责类型安全问题,但它们仍然缺少其他所有内容。

【问题讨论】:

  • 你不能让这个可靠,放弃这个想法。
  • 当你说“放弃这个想法”时,究竟是哪一部分?因为我需要具有行为的枚举类型;这不会消失。
  • 什么行为对枚举有意义?您列出的所有内容都使用 Enum 类上的静态方法存在。

标签: c# generics initialization


【解决方案1】:

我想出了这个 - 并不完全令人愉悦,但确实可以完成工作:

        public static IEnumerable<T> Values
        {
            get
            {
                if (nameRegistry.Count > 0)
                {
                    return nameRegistry.Values;
                }
                var aField = typeof (T).GetFields(
                                        BindingFlags.Public | BindingFlags.Static)
                                    .FirstOrDefault();

                if (aField != null)
                    aField.GetValue(null);

                return nameRegistry.Values;
            }
        }

编辑这是一个稍微不同的版本,应该解决 VinayC 在 cmets 中的担忧。问题是这样的:线程 A 调用 Values()。当 SomeEnum 的静态构造函数运行时,在它添加 V1 之后但在添加 V2 之前,线程 B 调用值。在最初编写的代码中,它会被传递一个 IEnumerable,它可能只产生 V1。因此,如果第二个线程在第一次调用任何特定类型的 Values() 期间调用,您可能会从 Values() 得到不正确的结果。

以下版本使用布尔标志,而不是依赖 nameRegistry 中的非零计数。在这个版本中,反射代码仍然有可能多次运行,但不再可能从 Values() 中得到错误的答案,因为当反射代码完成时,nameRegistry 保证被完全初始化。

        private static bool _initialized;
        public static IEnumerable<T> Values
        {
            get
            {
                if (_initialized)
                {
                    return nameRegistry.Values;
                }
                var aField = typeof(T).GetFields(
                                            BindingFlags.Public | BindingFlags.Static)
                                        .FirstOrDefault();
                if (aField != null)
                    aField.GetValue(null);
                _initialized = true;
                return nameRegistry.Values;
            }
        }

【讨论】:

  • @David,IMO,这里可能存在问题 - 考虑两个线程同时调用值(第一次) - 第二个线程可能得到错误的计数!
  • 有趣的地方。我不希望每次有人访问其中一个静态方法时都锁定开销......但也许锁定上面的反射代码,加上构造函数的胆量,会奏效吗?必须考虑一下,看看比赛可能在哪里。
  • @VinayC 在这种情况下我不太确定 - 请记住 V1、V2 等是静态字段,IIRC 规范确保静态构造函数只运行一次。当您最初调用 Values 时,问题是您实际上还没有触及 SomeEnum。一旦你这样做(使用反射代码),静态构造函数就会运行,这是线程安全的。另请考虑,如果您向 SomeEnum 添加一个静态 Values 属性,该属性只是传递给 AbstractEnum.Values,这也可以,但目标是避免在“枚举”类中编写更多代码。
  • @Xoltar,反射代码将触发SomeEnum 的静态构造函数,因此反射代码将停止,直到将 V1、V2 等添加到字典中。但是nameRegistry 是来自AbstractEnum 的静态文件,并且不会阻止对它的访问。所以当它被SomeEnum静态构造函数修改时,某些线程可能会访问它。
  • @Xoltar,值确实会返回不同的结果或者正确,它会将 IEnumerable 返回到变异数据 - 考虑线程 A 已调用 Values 首先触发 AbstractEnum 静态构造函数,然后SomeEnum 静态构造函数。考虑当 SomeEnum 静态构造函数正在运行时,线程 B 调用 Values - 如果在这个阶段,V1 已添加到 nameRegistry 然后 Values.Count 将返回 1,现在线程 B 来说是否也添加了 V2,它将得到 count = 2。
【解决方案2】:

诚然,我不知道 RichEnums 是什么,但是这个 C# 不能满足你的要求吗?

public enum SomeEnum
{
    V1,
    V2
}

class Program
{
    static void Main(string[] args)
    {
        var values = Enum.GetValues(typeof (SomeEnum));
        Console.WriteLine("Count: {0}", values.Length);
        foreach (SomeEnum e in values)
        {
            Console.WriteLine(e);
        }
    }
}

【讨论】:

  • 第一次需要向 SomeEnum 添加行为时它会停止工作。参见例如stackoverflow.com/questions/1376312/…
  • 我明白了。这是一个有趣的功能。我不确定我是否知道如何使用它。
  • 您可以使用 SomeEnum 上的扩展方法获得简单的行为。当然,这并不适用于所有事情,例如没有运算符重载。
  • 哦,那是真的。你最终会得到命名方法,我认为无论如何都是 Java 风格的。
【解决方案3】:

怎么样:

public class BaseRichEnum 
{
   public static InitializeAll()
   {
      foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
      {
        if (t.IsClass && !t.IsAbstract && typeof (BaseRichEnum).IsAssignableFrom(t))
        {
          t.GetMethod("Initialize").Invoke(null, null); //might want to use flags on GetMethod
        }
      }
   }
}

public class AbstractEnum<T> : BaseRichEnum where T : AbstractEnum<T>
{
    static readonly IDictionary<String, T> nameRegistry = new Dictionary<String, T>();

    readonly String name;

    protected AbstractEnum (String name)
    {
        this.name = name;
        nameRegistry[name] = (T) this;
    }

    public String Name {
        get {
            return name;
        }
    }

    public static T ValueOf(String name) {
        return nameRegistry[name];
    }

    public static IEnumerable<T> Values {
        get {
            return nameRegistry.Values;
        }
    }    
}

然后:

public class SomeEnum : AbstractEnum<SomeEnum> 
{

        public static readonly SomeEnum V1;
        public static readonly SomeEnum V2;

        public static void Initialize()
        {
          V1 = new SomeEnum("V1");
          V2 = new SomeEnum("V2"); 
        }

        SomeEnum(String name) : base(name) {
        }
    }

然后您必须在应用程序启动代码中调用 BaseRichEnum.InitializeAll()。我认为最好将这个简单的要求强加给客户端,从而使机制可见,而不是期望未来的维护者掌握静态时间初始化的微妙之处。

【讨论】:

    【解决方案4】:

    我不喜欢下面的解决方案,但是...

    public class AbstractEnum<T> where T : AbstractEnum<T>
    {
       ...
       private static IEnumerable<T> ValuesInternal {
            get {
                return nameRegistry.Values;
            }
       }
    
       public IEnumerable<T> Values {
         get {
           return ValuesInternal;
         }
       }
    }
    

    你必须使用SomeEnum.V1.Values - 我知道这很糟糕! 另一个需要一些工作的替代方案是

    public class AbstractEnum<T> where T : AbstractEnum<T>
    {
       ...
       protected static IEnumerable<T> ValuesInternal {
            get {
                return nameRegistry.Values;
            }
       }
    }
    
    public class SomeEnum : AbstractEnum<SomeEnum> {
      ...
    
      public static IEnumerable<SomeEnum> Values
      {
        get
        {
            return ValuesInternal;
        }
    
      }
    }
    

    我会选择第二个选项。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-06
      • 2012-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-16
      相关资源
      最近更新 更多