Skip to content

Instantly share code, notes, and snippets.

@soraphis
Last active Feb 23, 2021
Embed
What would you like to do?
a realproxy implementation of IRevertibleChangeTracking
/*
* Copyright © 2021 github.com/soraphis
* This work is free. You can redistribute it and/or modify it under the
* terms of the Do What The Fuck You Want To Public License, Version 2,
* as published by Sam Hocevar. See the COPYING file or http://www.wtfpl.net/
* for more details.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
namespace ConsoleApplication1.Utils
{
/// <summary>
/// Probably not the best name, but its just to create an interface for the IDE to show all useful members
/// </summary>
public interface IAutoImplementRevertibleChangeTracking<T> : IRevertibleChangeTracking
{
T Values { get; }
T Original { get; }
}
public class RevertibleChangeProxy<T> : RealProxy, IRemotingTypeInfo, IRevertibleChangeTracking where T : MarshalByRefObject
{
Dictionary<string, object> changeData = new Dictionary<string, object>();
private T _decorated;
private PropertyInfo[] props;
public RevertibleChangeProxy(T decorated) : base(typeof(T))
{
_decorated = decorated;
props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var pub in props)
{
changeData[pub.Name] = pub.GetValue(_decorated);
}
}
private const string IsChangedPropertyName = "get_" + nameof(IsChanged);
private const string ValuesPropertyName = "get_Values";
private const string OriginalPropertyName = "get_Original";
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
if (methodCall is null) return null;
try
{
switch (methodCall.MethodName)
{
case nameof(AcceptChanges):
AcceptChanges();
return new ReturnMessage(null, null, 0, methodCall.LogicalCallContext, methodCall);
case nameof(RejectChanges):
RejectChanges();
return new ReturnMessage(null, null, 0, methodCall.LogicalCallContext, methodCall);
case IsChangedPropertyName:
return new ReturnMessage(IsChanged, null, 0, methodCall.LogicalCallContext, methodCall);
case ValuesPropertyName:
return new ReturnMessage((T) this.GetTransparentProxy(), null, 0, methodCall.LogicalCallContext, methodCall);
case OriginalPropertyName:
return new ReturnMessage(_decorated, null, 0, methodCall.LogicalCallContext, methodCall);
default:
{
var propName = methodCall.MethodName.Substring(4);
if (methodCall.MethodName.StartsWith("set_") && changeData.ContainsKey(propName))
{
changeData[propName] = methodCall.InArgs[0];
return new ReturnMessage(null, null, 0, methodCall.LogicalCallContext, methodCall);
}else if(methodCall.MethodName.StartsWith("get_") && changeData.TryGetValue(propName, out var changedPropertyValue)){
return new ReturnMessage(changedPropertyValue, null, 0, methodCall.LogicalCallContext, methodCall);
}else{
var result = methodCall.MethodBase.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
}
}
}
}
catch (TargetInvocationException ex)
{
var exception = ex.InnerException;
return new ReturnMessage(exception, methodCall);
}
}
public void AcceptChanges()
{
foreach (var pub in props)
{
pub.SetValue(_decorated, changeData[pub.Name]);
}
}
public bool IsChanged => !props.All(x => x.GetValue(_decorated).Equals(changeData[x.Name]));
public void RejectChanges()
{
if (!IsChanged) return;
foreach (var pub in props)
{
changeData[pub.Name] = pub.GetValue(_decorated);
}
}
public static IAutoImplementRevertibleChangeTracking<T> Wrap<T>(T target) where T : MarshalByRefObject
{
return (IAutoImplementRevertibleChangeTracking<T>) new RevertibleChangeProxy<T>(target).GetTransparentProxy();
}
public bool CanCastTo(Type fromType, object o)
{
return fromType == typeof(IRevertibleChangeTracking) || fromType == typeof(IAutoImplementRevertibleChangeTracking<T>) || fromType == typeof(T);
}
public string TypeName
{
get => GetProxiedType().FullName;
set => throw new NotSupportedException();
}
}
}
/*
* Copyright © 2021 github.com/soraphis
* This work is free. You can redistribute it and/or modify it under the
* terms of the Do What The Fuck You Want To Public License, Version 2,
* as published by Sam Hocevar. See the COPYING file or http://www.wtfpl.net/
* for more details.
*/
using System;
using System.Diagnostics;
using ConsoleApplication1.Utils;
namespace ConsoleApplication1
{
public class MySettings : MarshalByRefObject
{
public int amountOfThings { get; set; } = 3;
public string nameOfStuff { get; set; } = "unnamed";
}
/// <summary>
/// This shows example usage of the RevertibleChangeProxy
/// </summary>
internal class Program
{
public static void Main(string[] args)
{
var settings = new MySettings(){ amountOfThings = 3, nameOfStuff = "unnamed"};
var changeableSettings = RevertibleChangeProxy<MySettings>.Wrap(settings);
Debug.Assert(! changeableSettings.IsChanged);
changeableSettings.Values.amountOfThings = 7;
Debug.Assert(changeableSettings.IsChanged);
Debug.Assert(changeableSettings.Values.amountOfThings == 7);
Debug.Assert(changeableSettings.Original.amountOfThings == 3);
changeableSettings.RejectChanges();
Debug.Assert(! changeableSettings.IsChanged);
Debug.Assert(changeableSettings.Values.amountOfThings == 3);
Debug.Assert(changeableSettings.Original.amountOfThings == 3);
changeableSettings.Values.amountOfThings = 7;
changeableSettings.AcceptChanges();
Debug.Assert(! changeableSettings.IsChanged);
Debug.Assert(changeableSettings.Values.amountOfThings == 7);
Debug.Assert(changeableSettings.Original.amountOfThings == 7);
settings.amountOfThings = 4; // dangerous! modifying the underlying object, will result in a 'changed' state on the changeable
changeableSettings.Original.amountOfThings = 4; // dangerous! modifying the underlying object, will result in a 'changed' state on the changeable
Debug.Assert(changeableSettings.IsChanged);
Debug.Assert(changeableSettings.Values.amountOfThings == 7);
Debug.Assert(changeableSettings.Original.amountOfThings == 4);
Console.ReadKey();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment