简体   繁体   English

生成一个强类型代理,当一个属性设置为另一个属性时,该代理可以跟踪属性名称而不是值的变化

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

Setup:设置:

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

Problem: I need to implement change tracking here for property names not values.问题:我需要在这里为属性名称而不是值实现更改跟踪。 Inside Runner.Run on the first line data.A = data.B I need to record somehow that "A" was set to "B" ( literally property names ) and then on the next line data.A = 1 I need to record that "A" was set to constant and say forget about it.Runner.Run的第一行data.A = data.B我需要以某种方式记录“A”设置为“B”(字面意思是属性名称),然后在下一行data.A = 1我需要记录那个“A”被设置为常数并说忘了它。

Constrains:约束:

  • When setting one property to another (eg A = B) that needs to be recorded将一个属性设置为需要记录的另一个属性(例如 A = B)时
  • When setting property to anything else (eg A = 1 or A = B * 2) this change needs to be forgotten (eg remember A only)将属性设置为其他任何值(例如 A = 1 或 A = B * 2)时,需要忘记此更改(例如仅记住 A)

Suppose this is the tracker contract being used:假设这是正在使用的跟踪器合约:

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

Question: I would like to somehow proxy the Data class so it still can be used in the interface code (eg Runner - is a whole bunch of a business logic code that uses Data ) INCLUDING strong-typing and it can track it's changes without modifying the code (eg there is lots of places like 'data.A = data.B').问题:我想以某种方式代理Data class 以便它仍然可以在接口代码中使用(例如Runner - 是一大堆使用Data的业务逻辑代码)包括强类型,它可以跟踪它的变化而无需修改代码(例如,有很多地方像'data.A = data.B')。

Is there any way to do it without resorting to I guess some magic involving IL generation?有没有办法在不求助于我猜一些涉及 IL 生成的魔法的情况下做到这一点?

Already investigated/tried:已经调查/尝试过:

  • PostSharp interceptors/Castle.DynamicProxy with interceptors - these alone cannot help.带有拦截器的 PostSharp 拦截器/Castle.DynamicProxy - 仅此而已。 The most I can get out of it is to have a value of data.B inside setter interceptor but not nameof(data.B) .我能从中得到的最多是在 setter 拦截器中有一个data.B的值,而不是nameof(data.B)
  • Compiler services - haven't found anything suitable here - getting the name of caller doesn't really help.编译器服务 - 在这里没有找到任何合适的东西 - 获取调用者的名字并没有真正帮助。
  • Runtine code generation - smth like proxy inherited from DynamicObject or using Relfection.Emit (TypeBuilder probably) - I lose typings. Runtine 代码生成 - 类似于从 DynamicObject 继承的代理或使用 Relfection.Emit(可能是 TypeBuilder) - 我失去了打字。

Current solution:当前解决方案:

Use the Tracker implementation of the abovementioned contract and pass it around into every function down the road.使用上述合约的 Tracker 实现,并将其传递给未来的每个 function。 Then instead of writing data.A = data.B use method tracker.SetFrom(x => xA, x => xB) - tracker holds a Data instance and so this works.然后不要写data.A = data.B使用方法tracker.SetFrom(x => xA, x => xB) - tracker 持有一个Data实例,所以这是可行的。 BUT in a real codebase it is easy to miss something and it just makes it way less readable.但是在真实的代码库中,很容易遗漏一些东西,而且只会降低可读性。

It is the closest the solution I've come up with.这是我想出的最接近的解决方案。 It isn't perfect as I still need to modify all the contracts/methods in the client code to use a new data model but at least all the logic stays the same.它并不完美,因为我仍然需要修改客户端代码中的所有合约/方法以使用新数据 model 但至少所有逻辑保持不变。

So I'm open for other answers.所以我愿意接受其他答案。

Here's the renewed 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; }
}

Basically I've copy-pasted the original Data model but changed all its properties to be aware of their names.基本上我已经复制粘贴了原始Data model 但更改了它的所有属性以了解它们的名称。

Then the tracker itself:然后是跟踪器本身:

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)
    {
    }
}

The interceptor for Castle.DynamicProxy: 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;
}

And the modified entry point:以及修改后的入口点:

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

The Map() function actually maps Data to NamedData filling in property names. Map() function 实际上将Data映射到NamedData填充属性名称。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 强类型模型中特定属性的自定义模型绑定 - Custom model binding for a particular property in a strongly-typed model 可以使用FormView自动生成一个Create Form(强类型) - Can FormView be used to automatically generate a Create Form (that is strongly-typed) 我无法通过Expression.Property访问对象的属性,除非所提供的表达式的类型是强类型的 - I can't access property on my object via Expression.Property unless the type of the fed expression is strongly-typed 获取强类型的子属性名称 - Getting sub property names strongly typed 强类型属性引用多个类,没有通用接口(C#) - Strongly-typed property reference to multiple classes with no common interface (C#) 自动生成强类型的AppSettings类 - Auto-generate a strongly-typed AppSettings class C#传递强类型属性名称列表 - C# passing a list of strongly typed property names 对web.config进行强类型调用而不重复属性名称? - Strongly typed calls into web.config without duplicating the property names? 如何在多个强类型视图之间保留对象属性值? - How do I retain object property values between more than one strongly typed view? ASP.NET强类型全球化资源值 - ASP.NET Strongly-typed globalization resource values
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM