Инициализация статических полей в C # универсальных типах - PullRequest
2 голосов
/ 01 марта 2011

Я понял из этого ответа , что инициализаторы статического поля 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.V1, OtherEnum.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 и вставив Values и ValueOf в подклассы, но я надеялся сохранить всю сложность в суперклассе и довести шаблон до минимум. Может ли кто-нибудь, чей C # -fu превосходит мой, придумать хитрость для того, чтобы сделать экземпляры подкласса «самореализующимися»?


Примечание: FWIW, это в Mono, на Mac OS. YM в MS .NET, в Windows, MV.


ETA: Для разработчиков моноглота C # (или даже разработчиков полиглотов, чей опыт ограничен языками, начинающимися с 'C'), задающихся вопросом WTF, я пытаюсь сделать: this . Перечисления C # заботятся о безопасности типов, но им все еще не хватает всего.

Ответы [ 4 ]

2 голосов
/ 01 марта 2011

Я придумал это - не совсем приятно, но делает работу:

        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 в комментариях. Проблема была в следующем: поток 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;
            }
        }
1 голос
/ 01 марта 2011

Как насчет:

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 () в коде запуска приложения. Я думаю, что лучше навязать это простое требование клиентам, тем самым делая видимым механизм, чем ожидать, что будущие сопровождающие поймут тонкости инициализации в статическом времени.

1 голос
/ 01 марта 2011

Правда, я не знаю, что такое 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);
        }
    }
}
0 голосов
/ 01 марта 2011

Мне не нравится приведенное ниже решение как таковое, но ...

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;
    }

  }
}

Я бы выбрал второй вариант.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...