2016-07-26 8 views
2

Я хочу сохранить некоторые поля поддержки свойств, объявленных в производных классах в защищенной Hashtable, содержащейся в базовом классе. Использование этого механизма в производных классах должно быть простым, насколько это возможно.MethodBase as Hashtable Key

Итак, могу ли я использовать MethodBase.GetCurrentMethod() для предоставления информации о вызове свойства (getter - свойства доступны только для чтения), поэтому его можно признать единственным и единственным свойством, имеющим доступ к этому конкретному полю поддержки?

EDIT:

В принципе, я хочу, чтобы реализовать шаблон:

private SomeClass _someProperty = null; 
private SomeClass SomeProperty 
{ 
    if (_someProperty == null) 
    { 
     _someProperty = new SomeClass(); 
    } 
    return _someProperty; 
} 

выглядеть примерно так:

private SomeClass SomeProperty 
{ 
    return GetProperty(delegate 
    { 
     var someProperty = new SomeClass(); 
     return someProperty; 
    }; 
} 

И в базовом классе

private System.Collections.Hashtable _propertyFields = new System.Collections.Hashtable(); 

    protected T GetProperty<T>(ConstructorDelegate<T> constructorBody) 
    { 
     var method = new System.Diagnostics.StackFrame(1).GetMethod(); 
     if (!_propertyFields.ContainsKey(method)) 
     { 
      var propertyObject = constructorBody.Invoke(); 
      _propertyFields.Add(method, propertyObject); 
     } 
     return (T)_propertyFields[method]; 
    } 

    protected delegate T ConstructorDelegate<T>(); 

Причина, по которой я хочу сделать это, - это упростить использование свойств. Я использую частные свойства для создания некоторых объектов и использования их вокруг класса. Но когда я храню свои поддерживающие поля в одном классе, у меня есть тот же доступ к ним, что и для свойств, поэтому я (означает, что пользователь, который будет создавать некоторые производные классы в будущем) может случайно использовать фоновое поле вместо свойства, поэтому я хотел ограничить доступ к полям резервного копирования, но позвольте создать объект и использовать его.

Я пытался использовать ObsoleteAttribute на отступающих таких областях, как это:

[Obsolete("Don't use this field. Please use corresponding property instead.")] 
    private SomeClass __someProperty; 
    private SomeClass _someProperty 
    { 
#pragma warning disable 0618 //Disable Obsolete warning for property usage. 
     get 
     { 
      if (__someProperty== null) 
      { 
       __someProperty = new SomeClass(); 
      } 
      return __someProperty ; 
     } 
#pragma warning restore 0618 //Restore Obsolete warning for rest of the code. 
    } 

Но, во-первых, я не могу заставить пользователя использовать этот шаблон, а во-вторых, это на много кода, чтобы написать в производном классе, который, как я уже упоминал выше, хочу быть как можно более простым.

+1

Как насчет использования некоторых примеров? Это почти похоже на ['ExpandoObject'] (https://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject (v = vs.110) .aspx) может подойти – weston

+0

Почему вы хотите хранить резервные поля в базовом классе? Ваш производный класс должен выставлять своих частных членов через общедоступные свойства, а частные поля должны оставаться закрытыми для производного класса. Вы пытаетесь поддерживать или совместно использовать состояние между производными объектами? –

+0

@IanR.О'Брайен, я отредактировал вопрос. – Szybki

ответ

2

Ни MethodBase, ни MemberInfo не правильно переопределяет Equals и GetHashCode функции, но использует по умолчанию RuntimeHelpers.GetHashCode и RuntimeHelpers.Equals. Таким образом, вы сможете сравнивать только один экземпляр, но не тот же контент. В большинстве случаев этого будет достаточно, поскольку время выполнения кэширует экземпляры для их повторного использования. Но нет гарантии, что это будет работать стабильно.

При работе с метаданными используйте то, что однозначно идентифицирует его. Например, MemberInfo.MetadataToken. Вы можете написать свой собственный компаратор и использовать его в Hashtable:

public class MethodBaseComparer : IEqualityComparer<MethodBase> 
{ 
    public bool Equals(MethodBase x, MethodBase y) 
    { 
     if (ReferenceEquals(x, y)) 
      return true; 

     if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) 
      return false; 

     return x.MetadataToken.Equals(y.MetadataToken) && 
       x.MethodHandle.Equals(y.MethodHandle); 
    } 

    public int GetHashCode(MethodBase obj) 
    { 
     return (obj.MetadataToken.GetHashCode() * 387)^obj.MethodHandle.GetHashCode(); 
    } 
} 

Это не очень хорошая идея, чтобы ограничить доступ через отражение в некоторых членов в качестве другого доверенного кода может использовать отражение для доступа к другим конфиденциальным данным обходя чеки. Рассмотрите возможность ограничения доступа путем перепроектирования ваших классов.

Также взгляните на Code Access Security.

Обновление в соответствии с вашими изменениями.

Вы сказали, что ваши свойства доступны только для чтения. Думаю, просто объявив их как readonly, это не ваш вариант. Похоже, вы хотите отложить инициализацию значений свойств. В этом случае вы не сможете объявить их как readonly. Правильно?

Возможно, вы можете?

Взгляните на класс Lazy<T>. Он недоступен в dotnet 2.0, но вы можете легко реализовать его или даже принять any existing implementation (just replace Func<T> with your delegate). Пример использования:

public class Foo 
{ 
    private readonly Lazy<int> _bar = new Lazy<int>(() => Environment.TickCount, true); 
    //   similar to your constructorBody - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

    private int Bar 
    { 
     get { return this._bar.Value; } 
    } 

    public void DoSomethingWithBar(string title) 
    { 
     Console.WriteLine("cur: {0}, foo.bar: {1} <- {2}", 
          Environment.TickCount, 
          this.Bar, 
          title); 
    } 
} 

Pros:

  1. Это ленивая инициализация, как вы хотите. Давайте проверим это:

    public static void Main() 
    { 
        var foo = new Foo(); 
    
        Console.WriteLine("cur: {0}", Environment.TickCount); 
    
        Thread.Sleep(300); 
        foo.DoSomethingWithBar("initialization"); 
    
        Thread.Sleep(300); 
        foo.DoSomethingWithBar("later usage"); 
    } 
    

    выход будет что-то вроде этого:

    cur: 433294875 
    cur: 433295171, foo.bar: 433295171 <- initialization 
    cur: 433295468, foo.bar: 433295171 <- later usage 
    

    Обратите внимание, значение инициализации при первом обращении и не изменено позже.

  2. Свойства защищены от записи компилятором - поле _bar - readonly, и у вас нет доступа к внутренним полям Lazy<T>. Таким образом, никакое случайное использование поля поддержки. Если вы попробуете, вы получите ошибку компиляции по типу несоответствия:

    CS0029 Не может неявно преобразовать тип System.Lazy<SomeClass> в SomeClass

    И даже если доступ к нему через this._bar.Value, ничего страшного не случится, и вы получите правильное значение, как если бы вы обращались к нему через this.Bar.

  3. Это намного проще, быстрее и легче читать и обслуживать.

  4. Безопасность потока из коробки.

Против: - (я не нашел)

Несколько центов о вашем hashtable -На дизайн:

  1. Вы (или кто-то, кто будет поддерживать ваш код) может случайно (или желательно) получить доступ и/или изменить либо целую хеш-таблицу, либо ее элементы, поскольку это просто обычная частная собственность.
  2. Hashtable - незначительный удар по производительности + получение stacktrace - огромный удар по производительности. Однако я не знаю, насколько это важно, зависит от того, как часто вы получаете доступ к своим свойствам.
  3. Было бы трудно читать и поддерживать.
  4. Небезопасный поток.
+0

Это выглядит красиво, но я могу получить MethodBase (либо методом MethodBase.GetCurrentMethod() в методе вызова (свойство getter), либо новым System.Diagnostics.StackFrame (1) .GetMethod() в методе, который читает значение. как я могу получить MethodInfo? – Szybki

+0

@Szybki 'MetadataToken' является членом' MemberInfo', который является родителем 'MethodBase' (и содержит' MethodHandle'), который является родителем 'MethodInfo'. Однако я изменил ответ на удалите путаницу. – lorond

+0

+1 и крошечное исправление: 'if (x == null || y == null) return false;' правильнее быть 'if (x == null) return y == null;' или еще лучше 'if (object.ReferenceEquals (x, null)) return object.ReferenceEquals (y, null);' . – Groo