簡體   English   中英

生成一個強類型代理,當一個屬性設置為另一個屬性時,該代理可以跟蹤屬性名稱而不是值的變化

[英]Generate a strongly-typed proxy that can track changes on property names not values when one property is set to another

設置:

public class Data
{
    public int A { get; set; }
    public int B { get; set; }
}

public class Runner
{
    public static void Run(Data data)
    {
        data.A = data.B;
        data.A = 1;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var data = new Data() { A = 1, B = 2 };
        Runner.Run(data);
    }
}

問題:我需要在這里為屬性名稱而不是值實現更改跟蹤。 Runner.Run的第一行data.A = data.B我需要以某種方式記錄“A”設置為“B”(字面意思是屬性名稱),然后在下一行data.A = 1我需要記錄那個“A”被設置為常數並說忘了它。

約束:

  • 將一個屬性設置為需要記錄的另一個屬性(例如 A = B)時
  • 將屬性設置為其他任何值(例如 A = 1 或 A = B * 2)時,需要忘記此更改(例如僅記住 A)

假設這是正在使用的跟蹤器合約:

void RecordChange(string setterName, string getterName);
void UnTrackChange(string setterName);

問題:我想以某種方式代理Data class 以便它仍然可以在接口代碼中使用(例如Runner - 是一大堆使用Data的業務邏輯代碼)包括強類型,它可以跟蹤它的變化而無需修改代碼(例如,有很多地方像'data.A = data.B')。

有沒有辦法在不求助於我猜一些涉及 IL 生成的魔法的情況下做到這一點?

已經調查/嘗試過:

  • 帶有攔截器的 PostSharp 攔截器/Castle.DynamicProxy - 僅此而已。 我能從中得到的最多是在 setter 攔截器中有一個data.B的值,而不是nameof(data.B)
  • 編譯器服務 - 在這里沒有找到任何合適的東西 - 獲取調用者的名字並沒有真正幫助。
  • Runtine 代碼生成 - 類似於從 DynamicObject 繼承的代理或使用 Relfection.Emit(可能是 TypeBuilder) - 我失去了打字。

當前解決方案:

使用上述合約的 Tracker 實現,並將其傳遞給未來的每個 function。 然后不要寫data.A = data.B使用方法tracker.SetFrom(x => xA, x => xB) - tracker 持有一個Data實例,所以這是可行的。 但是在真實的代碼庫中,很容易遺漏一些東西,而且只會降低可讀性。

這是我想出的最接近的解決方案。 它並不完美,因為我仍然需要修改客戶端代碼中的所有合約/方法以使用新數據 model 但至少所有邏輯保持不變。

所以我願意接受其他答案。

這是更新的Data model:

public readonly struct NamedProperty<TValue>
{
    public NamedProperty(string name, TValue value)
    {
        Name = name;
        Value = value;
    }

    public string Name { get; }
    public TValue Value { get; }

    public static implicit operator TValue (NamedProperty<TValue> obj)
        => obj.Value;

    public static implicit operator NamedProperty<TValue>(TValue value)
        => new NamedProperty<TValue>(null, value);
}

public interface ISelfTracker<T> 
    where T : class, ISelfTracker<T>
{
    Tracker<T> Tracker { get; set; }
}

public class NamedData : ISelfTracker<NamedData>
{
    public virtual NamedProperty<int> A { get; set; }
    public virtual NamedProperty<int> B { get; set; }

    public Tracker<NamedData> Tracker { get; set; }
}

基本上我已經復制粘貼了原始Data model 但更改了它的所有屬性以了解它們的名稱。

然后是跟蹤器本身:

public class Tracker<T> 
    where T : class, ISelfTracker<T>
{
    public T Instance { get; }
    public T Proxy { get; }

    public Tracker(T instance)
    {
        Instance = instance;
        Proxy = new ProxyGenerator().CreateClassProxyWithTarget<T>(Instance, new TrackingNamedProxyInterceptor<T>(this));
        Proxy.Tracker = this;
    }

    public void RecordChange(string setterName, string getterName)
    {
    }

    public void UnTrackChange(string setterName)
    {
    }
}

Castle.DynamicProxy 的攔截器:

public class TrackingNamedProxyInterceptor<T> : IInterceptor
    where T : class, ISelfTracker<T>
{
    private const string SetterPrefix = "set_";
    private const string GetterPrefix = "get_";

    private readonly Tracker<T> _tracker;

    public TrackingNamedProxyInterceptor(Tracker<T> proxy)
    {
        _tracker = proxy;
    }

    public void Intercept(IInvocation invocation)
    {
        if (IsSetMethod(invocation.Method))
        {
            string propertyName = GetPropertyName(invocation.Method);
            dynamic value = invocation.Arguments[0];

            var propertyType = value.GetType();
            if (IsOfGenericType(propertyType, typeof(NamedProperty<>)))
            {
                if (value.Name == null)
                {
                    _tracker.UnTrackChange(propertyName);
                }
                else
                {
                    _tracker.RecordChange(propertyName, value.Name);
                }

                var args = new[] { propertyName, value.Value };
                invocation.Arguments[0] = Activator.CreateInstance(propertyType, args);
            }
        }

        invocation.Proceed();
    }

    private string GetPropertyName(MethodInfo method)
        => method.Name.Replace(SetterPrefix, string.Empty).Replace(GetterPrefix, string.Empty);

    private bool IsSetMethod(MethodInfo method)
        => method.IsSpecialName && method.Name.StartsWith(SetterPrefix);

    private bool IsOfGenericType(Type type, Type openGenericType)
        => type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType;
}

以及修改后的入口點:

static void Main(string[] args)
{
    var data = new Data() { A = 1, B = 2 };
    NamedData namedData = Map(data);
    var proxy = new Tracker<NamedData>(namedData).Proxy;
    Runner.Run(proxy);

    Console.ReadLine();
}

Map() function 實際上將Data映射到NamedData填充屬性名稱。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM