使用表达式树修改字符串和子类/接口字符串属性



我精通表达式树的基础知识,但我在这个实现上遇到了困难:

我需要通过查找每个string属性并对其进行加密来修改类/接口的实例。 我还需要递归地对每个"子"类/接口属性执行相同的操作,一直到兔子洞。这也包括IEnumerable<T> where T : class。 我已经对加密进行了排序,但坦率地说,创建表达式尝试为传入的任何T执行此操作超出了我的理解范围。

我尝试通过反射来实现这一点,但性能很快成为一个问题 - 这是我到目前为止得到的:

使用这篇文章中的AggregateHelperPropertyHelper类,我能够执行基本的string加密:

简单的测试类:

public class SimpleEncryptionTest
{
public string String1 { get; set; }
}
public class NestedClassEncryptionTest
{
public string SomeString { get; set; }
public SimpleEncryptionTest Inner { get; set; } 
}
public class ListClassEncryptionTest
{
public List<string> StringList { get; set; }
}

我用来验证结果的单元测试:

[TestMethod]
public void Encryption_works_on_a_simple_class()
{
var testString = "this is only a test. If this had been a real emergency...";
var sut = new SimpleEncryptionTest() { String1 = testString };
sut.EncryptStringProperties();
Assert.AreNotEqual(testString, sut.String1);
}
[TestMethod]
public void Round_trip_works_on_a_simple_class()
{
var testString = "what string should I use?";
var sut = new SimpleEncryptionTest() { String1 = testString };
sut.EncryptStringProperties();
sut.DecryptStringProperties();
Assert.AreEqual(testString, sut.String1);
}
[TestMethod]
public void Round_trip_works_in_nested_class_scenario()
{
var outerString = "what is your name?";
var innerString = "Tony; what's your name?";
var sut = new NestedClassEncryptionTest{
SomeString = outerString,
Inner = new SimpleEncryptionTest(){String1 = innerString }
};
sut.EncryptStringProperties();
Assert.AreNotEqual(outerString, sut.SomeString);
Assert.AreNotEqual(innerString, sut.Inner.String1);
sut.DecryptStringProperties();
Assert.AreEqual(outerString, sut.SomeString);
Assert.AreEqual(innerString, sut.Inner.String1);
}
[TestMethod]
public void Round_trip_works_on_lists()
{
var testone = "one";
var testtwo = "two";
var testStrings = new List<string>() { testone, testtwo };
var sut = new ListClassEncryptionTest() { StringList = testStrings };
sut.EncryptStringProperties();
Assert.AreNotEqual(testone, sut.StringList[0]);
Assert.AreNotEqual(testtwo, sut.StringList[1]);
sut.DecryptStringProperties();
Assert.AreEqual(testone, sut.StringList[0]);
Assert.AreEqual(testtwo, sut.StringList[1]);
}

以及我用来加密/解密值的扩展方法:

/// <summary>
/// Iterates through an Object's properties and encrypts any strings it finds, 
/// recursively iterating any class or interface Properties.
/// </summary>
/// <typeparam name="T">Any type</typeparam>
/// <param name="genericObj">The object to encrypt</param>
/// <returns>The original object, with its string values encrypted.</returns>
public static T EncryptStringProperties<T>(this T obj)
where T : class
{
return Crypt(obj, EncryptStringAction);
}
/// <summary>
/// Iterates through an Object's properties and decrypts any strings it finds, 
/// recursively iterating any class or interface Properties.
/// </summary>
/// <typeparam name="T">Any type</typeparam>
/// <param name="genericObj">The object to decrypt</param>
/// <returns>The original object, with its string values decrypted.</returns>
public static T DecryptStringProperties<T>(this T obj)
{
return Crypt(obj, DecryptStringAction);
}
private static T Crypt<T>(T obj, Action<PropertyHelper<T, string>, T> action)
{
var stringProperties = new AggregateHelper<T, string>();
foreach (var property in stringProperties.Properties)
{
var propertyHelper = stringProperties[property];
action(propertyHelper, obj);
}
// how do I find the nested classes, interfaces and lists
// using Expression Trees to feed them through the same processing?
return obj;
}
private static void EncryptStringAction<T>(PropertyHelper<T, string> prop, T genericObj)
{
var plainTextValue = (string)prop.GetValue(genericObj);
prop.SetValue(genericObj, plainTextValue.ToEncryptedString());
}
private static void DecryptStringAction<T>(PropertyHelper<T, string> prop, T genericObj)
{
var encryptedValue = (string)prop.GetValue(genericObj);
prop.SetValue(genericObj, encryptedValue.ToDecryptedString());
}

就目前而言,这很好用;它加密任何object上的string属性,但我需要更进一步 - 我需要某种递归方式"进入兔子洞" - 创建一个AggregateHelper,选择作为实例对象(类或接口)的属性并通过.EncryptStringProperties()扩展方法提供这些属性,以及处理具有string值的任何IEnumerable

尝试以下扩展。它为每个对象生成替换表达式树,并编译委托以快速替换字符串。所有委托都已缓存,替换应快速。它还处理递归引用,并应省略堆栈溢出。

StringPropReplacer.ReplaceStrings(obj, s => Encrypt(s));
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace StringReplacer
{
public static class StringPropReplacer
{
private static ParameterExpression _strParam = Expression.Parameter(typeof(string), "str");
private static ParameterExpression _originalValue = Expression.Variable(typeof(string), "original");
private static ParameterExpression _newValue = Expression.Variable(typeof(string), "newValue");
private static ParameterExpression _visitedParam = Expression.Parameter(typeof(HashSet<object>), "visited");
private static ParameterExpression _replacerParam = Expression.Parameter(typeof(Func<string, string>), "replacer");
private static ParameterExpression[] _blockVariables = new [] {_originalValue, _newValue};
private static void ReplaceObject<T>(T obj, HashSet<object> visited, Func<string, string> replacer)
{
ReflectionHolder<T>.ReplaceObject(obj, visited, replacer);
}
private static void ReplaceObjects<T>(IEnumerable<T> objects, HashSet<object> visited, Func<string, string> replacer)
{
ReflectionHolder<T>.ReplaceObjects(objects, visited, replacer);
}
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
where T : notnull
{
public static IEqualityComparer<T> Default = new ObjectReferenceEqualityComparer<T>();
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
if (obj == null)
return 0;
return RuntimeHelpers.GetHashCode(obj);
}
#endregion
}

private static class ReflectionHolder<T>
{
private static Action<T, HashSet<object>, Func<string, string>> _replacer;
static ReflectionHolder()
{
var objParam = Expression.Parameter(typeof(T), "obj");
var blockBody = new List<Expression>();
foreach (var prop in typeof(T).GetProperties())
{
if (prop.PropertyType == typeof(string) && prop.CanRead && prop.CanWrite)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Assign(_originalValue, propExpression));
blockBody.Add(Expression.Assign(_newValue, Expression.Invoke(_replacerParam, _originalValue)));
blockBody.Add(Expression.IfThen(Expression.NotEqual(_originalValue, _newValue),
Expression.Assign(propExpression, _newValue)));
}
else if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType))
{
var intf = prop.PropertyType
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (intf != null)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Call(typeof(StringPropReplacer), "ReplaceObjects",
intf.GetGenericArguments(), propExpression, _visitedParam, _replacerParam
));
}
}
else if (prop.PropertyType.IsClass)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Call(typeof(StringPropReplacer), "ReplaceObject",
new[] {prop.PropertyType}, propExpression, _visitedParam, _replacerParam
));
}

}
if (blockBody.Count == 0)
_replacer = (o, v, f) => { };
else
{
var replacerExpr = Expression.Lambda<Action<T, HashSet<object>, Func<string, string>>>(
Expression.Block(_blockVariables, blockBody), objParam, _visitedParam, _replacerParam);
_replacer = replacerExpr.Compile();
}
}
public static void ReplaceObject(T obj, HashSet<object> visited, Func<string, string> replacer)
{
if (obj == null)
return;
if (!visited.Add(obj))
return;

_replacer(obj, visited, replacer);
}
public static void ReplaceObjects(IEnumerable<T> objects, HashSet<object> visited, Func<string, string> replacer)
{
if (objects == null)
return;
if (!visited.Add(objects))
return;
foreach (var obj in objects)
{
ReplaceObject(obj, visited, replacer);   
}
}
}
public static void ReplaceStrings<T>(T obj, Func<string, string> replacer)
{
ReplaceObject(obj, new HashSet<object>(ObjectReferenceEqualityComparer<object>.Default), replacer);
}
}
}

最新更新