C#使用表達(dá)式樹(LambdaExpression)動態(tài)更新類的屬性值(示例代碼)
有看過我之前發(fā)表過的C#相關(guān)文章分享和閱讀過我代碼的朋友們可能會在我的代碼里面經(jīng)常看到各種各樣的λ表達(dá)式動態(tài)拼接,C#的λ表達(dá)式樹是一個好東西,也是別的語言學(xué)不來的,熟悉掌握λ表達(dá)式就能夠?qū)崿F(xiàn)各種場景的個性化操作,如動態(tài)拼接查詢條件、排序方式等,也能夠?qū)崿F(xiàn)替代反射的高性能操作,比如我們常用到的IQueryable和IEnumerable,每個擴(kuò)展方法就全是λ表達(dá)式樹。
本文給大家分享C#使用表達(dá)式樹(LambdaExpression)動態(tài)更新類的屬性值的相關(guān)知識,在某些業(yè)務(wù)中會遇到需要同步兩個類的屬性值的情況,而且有些字段是要過濾掉的。如果手動賦值則需要寫很多重復(fù)的代碼:
public class Teacher
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Age { get; set; }
}
public class Student
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Age { get; set; }
}
/// <summary>
/// 比如需要把teacher的某些屬性值賦給student,而id不需要賦值
/// </summary>
/// <param name="student"></param>
/// <param name="teacher"></param>
public static void SetProperty(Student student, Teacher teacher)
{
if (student.Name != teacher.Name)
{
student.Name = teacher.Name;
}
if (student.Age != teacher.Age)
{
student.Age = teacher.Age;
}
}
使用反射的話性能考慮,嘗試寫一個擴(kuò)展方法使用lambda表達(dá)式樹去構(gòu)建一個方法
public static class ObjectExtensions
{
/// <summary>
/// 緩存表達(dá)式
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
public static class MapperAccessor<TSource, TTarget>
{
private static Action<TSource, TTarget, string[]> func { get; set; }
public static TSource Set(TSource source, TTarget target, params string[] properties)
{
if (func == null)
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
if (properties.Length == 0)
{
//get all properties
if (sourceType == targetType)
{
//如果是相同類型則獲取所有屬性
properties = sourceType.GetProperties(bindingFlags).Select(x => x.Name)
.ToArray();
}
else
{
//如果沒有傳指定的屬性則默認(rèn)獲取同名屬性
List<PropertyInfo> propertyInfos = new List<PropertyInfo>();
foreach (var property in sourceType.GetProperties(bindingFlags))
{//不同類型指定同名且類型相同的屬性
var targetProperty = targetType.GetProperty(property.Name, bindingFlags);
if (targetProperty != null && targetProperty.PropertyType == property.PropertyType)
{
propertyInfos.Add(property);
}
}
properties = propertyInfos.Select(x => x.Name).ToArray();
}
}
//定義lambda 3個參數(shù)
var s = Expression.Parameter(typeof(TSource), "s");
var t = Expression.Parameter(typeof(TTarget), "t");
var ps = Expression.Parameter(typeof(string[]), "ps");
//獲取泛型擴(kuò)展方法Contains
var methodInfo = typeof(Enumerable).GetMethods().FirstOrDefault(e => e.Name == "Contains" && e.GetParameters().Length == 2);
if (methodInfo == null)
{
// properties.Contains()
throw new NullReferenceException(nameof(methodInfo));
}
MethodInfo genericMethod = methodInfo.MakeGenericMethod(typeof(String));//創(chuàng)建泛型方法
List<BlockExpression> bs = new List<BlockExpression>();
foreach (string field in properties)
{
//獲取兩個類型里面的屬性
var sourceField = Expression.Property(s, field);
var targetField = Expression.Property(t, field);
//創(chuàng)建一個條件表達(dá)式
var notEqual = Expression.NotEqual(sourceField, targetField);//sourceField!=targetField
var method = Expression.Call(null, genericMethod, ps, Expression.Constant(field));//ps.Contains(f);
//構(gòu)建賦值語句
var ifTrue = Expression.Assign(sourceField, targetField);
//拼接表達(dá)式 sourceField!=targetField&&ps.Contains(f)
var condition = Expression.And(notEqual, Expression.IsTrue(method));
//判斷是否相同,如果不相同則賦值
var expression = Expression.IfThen(condition, ifTrue);
bs.Add(Expression.Block(expression));
}
var lambda = Expression.Lambda<Action<TSource, TTarget, string[]>>(Expression.Block(bs), s, t, ps);
func = lambda.Compile();
}
func.Invoke(source, target, properties);
return source;
}
}
/// <summary>
/// 通過目標(biāo)類更新源類同名屬性值
/// </summary>
/// <typeparam name="TSource">待更新的數(shù)據(jù)類型</typeparam>
/// <typeparam name="TTarget">目標(biāo)數(shù)據(jù)類型</typeparam>
/// <param name="source">源數(shù)據(jù)</param>
/// <param name="target">目標(biāo)數(shù)據(jù)</param>
/// <param name="properties">要變更的屬性名稱</param>
/// <returns>返回源數(shù)據(jù),更新后的</returns>
public static TSource SetProperties<TSource, TTarget>(this TSource source, TTarget target, params string[] properties)
{
return MapperAccessor<TSource, TTarget>.Set(source, target, properties);
}
}編寫測試方法
/// <summary>
/// 比如需要把teacher的某些屬性值賦給student,而id不需要賦值
/// </summary>
/// <param name="student"></param>
/// <param name="teacher"></param>
public static void SetProperty(Student student, Teacher teacher)
{
if (student.Name != teacher.Name)
{
student.Name = teacher.Name;
}
if (student.Age != teacher.Age)
{
student.Age = teacher.Age;
}
}
public static void SetProperty2(Student student, Teacher teacher, params string[] properties)
{
var sourceType = student.GetType();
var targetType = teacher.GetType();
foreach (var property in properties)
{
var aP = sourceType.GetProperty(property);
var bP = targetType.GetProperty(property);
var apValue = aP.GetValue(student);
var bpValue = bP.GetValue(teacher);
if (apValue != bpValue)
{
aP.SetValue(student, bpValue);
}
}
}
static (List<Student>, List<Teacher>) CreateData(int length)
{
var rd = new Random();
(List<Student>, List<Teacher>) ret;
ret.Item1 = new List<Student>();
ret.Item2 = new List<Teacher>();
for (int i = 0; i < length; i++)
{
Student student = new Student()
{
Id = Guid.NewGuid(),
Name = Guid.NewGuid().ToString("N"),
Age = rd.Next(1, 100)
};
ret.Item1.Add(student);
Teacher teacher = new Teacher()
{
Id = Guid.NewGuid(),
Name = Guid.NewGuid().ToString("N"),
Age = rd.Next(1, 100)
};
ret.Item2.Add(teacher);
}
return ret;
}
static void Main(string[] args)
{
var length = 1000000;
var data = CreateData(length);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < length; i++)
{
SetProperty(data.Item1[i], data.Item2[i]);
}
sw.Stop();
Console.WriteLine($"手寫方法耗時:{sw.ElapsedMilliseconds}ms");
data.Item1.Clear();
data.Item2.Clear();
var data2 = CreateData(length);
sw.Restart();
for (int i = 0; i < length; i++)
{
data2.Item1[i].SetProperties(data2.Item2[i], nameof(Student.Age), nameof(Student.Name));
}
data2.Item1.Clear();
data2.Item2.Clear();
sw.Stop();
Console.WriteLine($"lambda耗時:{sw.ElapsedMilliseconds}ms");
var data3 = CreateData(length);
sw.Restart();
for (int i = 0; i < length; i++)
{
SetProperty2(data3.Item1[i], data3.Item2[i], nameof(Student.Age), nameof(Student.Name));
}
sw.Stop();
Console.WriteLine($"反射耗時:{sw.ElapsedMilliseconds}ms");
data3.Item1.Clear();
data3.Item2.Clear();
Console.ReadKey();
}
可以看到性能和手寫方法之間的差距,如果要求比較高還是手寫方法,如果字段多的話寫起來是很痛苦的事。但是日常用這個足夠了,而且是擴(kuò)展方法,通用性很強。
到此這篇關(guān)于C#使用表達(dá)式樹(LambdaExpression)動態(tài)更新類的屬性值的文章就介紹到這了,更多相關(guān)C#表達(dá)式樹類的屬性值內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#先判斷是否存在再創(chuàng)建文件夾或文件與遞歸計算文件夾大小
這篇文章介紹了C#先判斷是否存在再創(chuàng)建文件夾或文件與遞歸計算文件夾大小的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
C#使用RichTextBox實現(xiàn)替換文字及改變字體顏色功能示例
這篇文章主要介紹了C#使用RichTextBox實現(xiàn)替換文字及改變字體顏色功能,結(jié)合實例形式洗了C#中RichTextBox組件文字替換及改變字體顏色相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
C#數(shù)組學(xué)習(xí)相關(guān)資料整理
最近開始學(xué)習(xí)c#,并有幸接觸到了數(shù)組方便的操作,感覺確實不錯,這里簡單的整理下c#相關(guān)的學(xué)習(xí)資料,方便大家學(xué)習(xí)2012-09-09

