Как динамически реализовать шаблон прокси?

Я пересматриваю свое отслеживание классов (грязная логика), которое я написал в прошлом году. В настоящее время у меня есть базовый класс uber, который занимается отслеживанием всего состояния, но каждое свойство, значения которого мне нужно отслеживать, должно придерживаться стандартного get { return _x; } set { _isDirty = true; _x = value; } способа работы.

Поиграв с Entity Framework и ознакомившись с шаблоном прокси, я надеялся, что лучший способ реализовать мою логику IsDIrty, в то же время имея возможность использовать автоматически реализуемые свойства?

Честно говоря, я понятия не имею, о чем говорю. Есть ли способ сделать что-то вроде следующего:

public class Customer : ITrackable
{
    [TrackState(true)] // My own attribute
    public virtual string Name { get;set;}

    [TrackState(true)]
    public virtual  int Age { get;set;}

    // From ITrackable
    public bool IsDirty { get; protected set; }

}

А затем реализуйте динамический прокси, который будет использовать отражение (или другое волшебное решение) для вызова другого метода, прежде чем устанавливать значения свойств с атрибутом TrackState.

Очевидно, я мог бы легко сделать это, создав физический прокси-класс и используя IoC:

public class CustomerProxy : Customer
{
    Customer _customer;

    public override string Name 
    {
        get { return _customer.Name; }
        set { IsDirty = true; return _customer.Name; }
    }

    // Other properties
}

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


person djdd87    schedule 13.09.2010    source источник
comment
joe.truemesh.com/blog//000181.html   -  person Bozho    schedule 13.09.2010
comment
совершенно не имеет значения: убойное имя пользователя, мне оно нравится, лол.   -  person TheXenocide    schedule 01.06.2011


Ответы (2)



PostSharp может помочь.

Или, если вам так хочется, вы можете написать для этого свой собственный IL-рерайтер. Mono.Cecil – отличная библиотека, с которой вам будет очень легко работать. Вот быстрый состав:

class Program {

  static ModuleDefinition _module;

  static void Main(string[] args) {
    // the argument is the assembly path
    _module = ModuleDefinition.ReadModule(args[0]);
    var trackables = _module.Types.
      Where(type => type.Interfaces.Any(tr => tr.Name == "ITrackable"));
    var properties = trackables.SelectMany(type => type.Properties);
    var trackableProperties = properties.
      Where(property => property.CustomAttributes.
        Any(ca => ca.Constructor.DeclaringType.Name == "TrackStateAttribute"));
    trackableProperties.
      Where(property => property.SetMethod != null).
      ToList().
      ForEach(property => CallIsDirty(property.SetMethod));
    _module.Write(args[0]);
  }

  private static void CallIsDirty(MethodDefinition setter) {
    Console.WriteLine(setter.Name);

    var isDirty = setter.DeclaringType.Methods.
      Single(method => method.Name == "set_IsDirty");
    var reference = new MethodReference(isDirty.Name,
      _module.Import(typeof(void))) {
        DeclaringType = setter.DeclaringType,  
        HasThis = true,
        CallingConvention = MethodCallingConvention.Default
      };
    reference.Parameters.Add(new ParameterDefinition(
      _module.Import(typeof(bool))));
    var IL = setter.Body.GetILProcessor();
    var param0 = IL.Create(OpCodes.Ldarg_0);
    var param1 = IL.Create(OpCodes.Ldc_I4_1);
    var call = IL.Create(OpCodes.Call, reference);
    IL.InsertBefore(setter.Body.Instructions[0], call);
    IL.InsertBefore(setter.Body.Instructions[0], param1);
    IL.InsertBefore(setter.Body.Instructions[0], param0);
  }
}

Он использует эти помощники:

public class TrackStateAttribute : Attribute { }

public interface ITrackable { bool IsDirty { get; } }

Пример кода:

public class Customer : ITrackable {
  [TrackState] public string Name { get; set; }
  [TrackState] public int Age { get; set; }
  public bool IsDirty { get; protected set; }
}

Предполагается, что свойство IsDirty также будет иметь установщик.

person Jordão    schedule 13.09.2010
comment
+1 Спасибо! У меня уже был Castle.Core в моем проекте, поэтому вместо него я выбрал DynamicProxy. - person djdd87; 14.09.2010