[英]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:约束:
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:已经调查/尝试过:
data.B
inside setter interceptor but not nameof(data.B)
.我能从中得到的最多是在 setter 拦截器中有一个data.B
的值,而不是nameof(data.B)
。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.