[英]Foreach loop using Expression trees
我在構建動態表達式樹和表達式/語句樹時看到了這個問題,由於我是表達式樹的新手,我仍然在努力理解如何實現我想要的。
一個人為的對象在下面
public class TestObject
{
public TestObject()
{
ClassList = new List<Class>();
}
public int Age { get; set; }
public List<Class> ClassList { get; set; }
}
public class Class
{
public string Name { get; set; }
public int ClassId { get; set; }
}
在運行時,我遍歷每個屬性並生成一個委托,該委托將轉換為該屬性的字符串。 我已經完成了所有工作。 我現在必須處理的問題是,對於 List 類型,我需要能夠對 ClassList 屬性中的每個項目應用一組操作,因此我需要一個 foreach 來允許我這樣做。
我目前有這個
//type==TestObject at runtime
//propertyName == "ClassList"
ParameterExpression recordExpression = Expression.Parameter(type, "record");
memberExpression = MemberExpression.Property(recordExpression, propertyName);
Type getEnumerableDelegateType =
typeof(Func<,>).MakeGenericType(new Type[] { type, memberExpression.Type});
var getList = Expression.Lambda(getEnumerableDelegateType, memberExpression, recordExpression);
GetList 在編譯和調用時按預期返回 List。 我正在努力解決的是如何創建一個表達式,該表達式將使用 lambda 表達式的結果並應用我已經為每個 Class 項創建的一組操作對其進行迭代。
最終我正在尋找一個 lambda 簽名來匹配下面的整體動作簽名
var getListFunc = new Func<TestObject, List<Class>>((TestObject obj1) => obj1.ClassList);
Action<List<Class>> listAction = delegate(List<Class> data)
{
foreach (var dataChannelWithUnitse in data)
{
//Apply generated delegate
}
};
Action<TestObject> overallAction = delegate(TestObject data)
{
var x = getListFunc.Invoke(data);
listAction.Invoke(x as List<Class>);
};
感謝任何幫助,以幫助我了解如何做到這一點。
我目前得到了這個,它是從范圍''引用的'TestObject'類型的變量'Input'的例外,但它沒有定義
var typeParam = Expression.Parameter(type, "Input");
var listVariable = Expression.Variable(memberExpression.Type, "List");
var enumerator = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(dataType));
var enumeratorType = typeof(IEnumerator<>).MakeGenericType(dataType);
var enumerableType = typeof(IEnumerable<>).MakeGenericType(dataType);
var enumerableParam = Expression.Parameter(enumerableType, "ExtractedCollection");
var getEnumeratorFunc = Expression.Call(enumerableParam, enumerableType.GetMethod("GetEnumerator"));
var getEnumeratorLambda = Expression.Lambda(getEnumeratorFunc, enumerableParam);
var t1 = Expression.Assign(listVariable, Expression.Invoke(getListLambda, typeParam));
var t2 = Expression.Assign(enumerator, Expression.Invoke(getEnumeratorLambda, listVariable));
var @break = Expression.Label();
var funcBlock = Expression.Block(
new ParameterExpression[] { listVariable, enumerator},
t1,
t2,
Expression.Loop(
Expression.IfThenElse(
Expression.NotEqual(Expression.Call(enumerator,typeof(IEnumerator).GetMethod("MoveNext")),Expression.Constant(false)),
Expression.Invoke(enumerableExpressions[0],Expression.Property(enumerator, "Current")),
Expression.Break(@break))
, @break), typeParam);
Expression<Action<TestObject>> lm = Expression.Lambda<Action<TestObject>>(funcBlock,recordExpression);
var d = lm.Compile(); **//this is exceptioning with " variable 'Input' of type 'TestObject' referenced from scope '', but it is not defined**
我在你的問題中間迷路了(如果我解釋錯了,請告訴我,我會重新深入研究),但我認為這就是你所追求的:
public static Expression ForEach(Expression collection, ParameterExpression loopVar, Expression loopContent)
{
var elementType = loopVar.Type;
var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
var getEnumeratorCall = Expression.Call(collection, enumerableType.GetMethod("GetEnumerator"));
var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
// The MoveNext method's actually on IEnumerator, not IEnumerator<T>
var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
var breakLabel = Expression.Label("LoopBreak");
var loop = Expression.Block(new[] { enumeratorVar },
enumeratorAssign,
Expression.Loop(
Expression.IfThenElse(
Expression.Equal(moveNextCall, Expression.Constant(true)),
Expression.Block(new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
loopContent
),
Expression.Break(breakLabel)
),
breakLabel)
);
return loop;
}
要使用它,您需要提供一個要迭代的集合、一個要替換到循環體中的表達式,以及一個由循環體表達式使用的 ParameterExpression,它將在每次循環迭代時分配給循環變量。
我認為有時例子勝於雄辯......
var collection = Expression.Parameter(typeof(List<string>), "collection");
var loopVar = Expression.Parameter(typeof(string), "loopVar");
var loopBody = Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), loopVar);
var loop = ForEach(collection, loopVar, loopBody);
var compiled = Expression.Lambda<Action<List<string>>>(loop, collection).Compile();
compiled(new List<string>() { "a", "b", "c" });
編輯:正如 Jeroem Mostert 在評論中正確指出的那樣,這並不能完全反映 foreach 循環的“真實”行為:這將確保它處理枚舉器。 (它還會為每次迭代創建一個新的循環變量實例,但這對表達式沒有意義)。 如果您感到有足夠的動力,只需轉動手柄即可實現這一點!
對於在家觀看的人,我有一個類似的方法來生成“for”循環:
public static Expression For(ParameterExpression loopVar, Expression initValue, Expression condition, Expression increment, Expression loopContent)
{
var initAssign = Expression.Assign(loopVar, initValue);
var breakLabel = Expression.Label("LoopBreak");
var loop = Expression.Block(new[] { loopVar },
initAssign,
Expression.Loop(
Expression.IfThenElse(
condition,
Expression.Block(
loopContent,
increment
),
Expression.Break(breakLabel)
),
breakLabel)
);
return loop;
}
這等效於以下語句,其中偽變量與上述方法中的表達式匹配:
for (loopVar = initValue; condition; increment)
{
loopContent
}
同樣,loopContent、condition 和 increment 是使用 loopVar 的表達式,並且每次迭代都會分配 loopVar。
relative_random 的解決方案很棒,但foreach
處理其他幾種情況。 檢查這些指向 SharpLab 的鏈接以驗證每個鏈接中生成的內容:
IEnumerable<T>
,它會在調用Dispose()
之前檢查枚舉器是否為null
。GetEnumerator()
返回的類型。 在調用Dispose()
之前,枚舉數被IDisposable
為IDisposable
。IDisposable
, as
使用的檢查,如果枚舉工具IDisposable
。 (???)null
的檢查就會消失。IDisposable
, try
/ finally
會消失。 使用GetEnumerator()
返回的類型非常重要,因此值類型枚舉器不會被裝箱。 System.Collections.Generic
所有集合都具有值類型枚舉器,因為對其方法的調用不是虛擬的,從而帶來更好的性能。
將所有結果放在一起會產生以下代碼:
static partial class ExpressionEx
{
public static Expression ForEach<TSource>(Expression enumerable, Expression loopContent)
{
var enumerableType = enumerable.Type;
var getEnumerator = enumerableType.GetMethod("GetEnumerator");
if (getEnumerator is null)
getEnumerator = typeof(IEnumerable<>).MakeGenericType(typeof(TSource)).GetMethod("GetEnumerator");
var enumeratorType = getEnumerator.ReturnType;
var enumerator = Expression.Variable(enumeratorType, "enumerator");
return Expression.Block(new[] { enumerator },
Expression.Assign(enumerator, Expression.Call(enumerable, getEnumerator)),
EnumerationLoop(enumerator, loopContent));
}
public static Expression ForEach<TSource>(Expression enumerable, ParameterExpression loopVar, Expression loopContent)
{
var enumerableType = enumerable.Type;
var getEnumerator = enumerableType.GetMethod("GetEnumerator");
if (getEnumerator is null)
getEnumerator = typeof(IEnumerable<>).MakeGenericType(typeof(TSource)).GetMethod("GetEnumerator");
var enumeratorType = getEnumerator.ReturnType;
var enumerator = Expression.Variable(enumeratorType, "enumerator");
return Expression.Block(new[] { enumerator },
Expression.Assign(enumerator, Expression.Call(enumerable, getEnumerator)),
EnumerationLoop(enumerator,
Expression.Block(new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumerator, "Current")),
loopContent)));
}
static Expression EnumerationLoop(ParameterExpression enumerator, Expression loopContent)
{
var loop = While(
Expression.Call(enumerator, typeof(IEnumerator).GetMethod("MoveNext")),
loopContent);
var enumeratorType = enumerator.Type;
if (typeof(IDisposable).IsAssignableFrom(enumeratorType))
return Using(enumerator, loop);
if (!enumeratorType.IsValueType)
{
var disposable = Expression.Variable(typeof(IDisposable), "disposable");
return Expression.TryFinally(
loop,
Expression.Block(new[] { disposable },
Expression.Assign(disposable, Expression.TypeAs(enumerator, typeof(IDisposable))),
Expression.IfThen(
Expression.NotEqual(disposable, Expression.Constant(null)),
Expression.Call(disposable, typeof(IDisposable).GetMethod("Dispose")))));
}
return loop;
}
public static Expression Using(ParameterExpression variable, Expression content)
{
var variableType = variable.Type;
if (!typeof(IDisposable).IsAssignableFrom(variableType))
throw new Exception($"'{variableType.FullName}': type used in a using statement must be implicitly convertible to 'System.IDisposable'");
var getMethod = typeof(IDisposable).GetMethod("Dispose");
if (variableType.IsValueType)
{
return Expression.TryFinally(
content,
Expression.Call(Expression.Convert(variable, typeof(IDisposable)), getMethod));
}
if (variableType.IsInterface)
{
return Expression.TryFinally(
content,
Expression.IfThen(
Expression.NotEqual(variable, Expression.Constant(null)),
Expression.Call(variable, getMethod)));
}
return Expression.TryFinally(
content,
Expression.IfThen(
Expression.NotEqual(variable, Expression.Constant(null)),
Expression.Call(Expression.Convert(variable, typeof(IDisposable)), getMethod)));
}
public static Expression While(Expression loopCondition, Expression loopContent)
{
var breakLabel = Expression.Label();
return Expression.Loop(
Expression.IfThenElse(
loopCondition,
loopContent,
Expression.Break(breakLabel)),
breakLabel);
}
}
不帶loopVar
的ForEach
可用於在不獲取項目的情況下進行枚舉。 這就是Count()
實現的情況。
編輯: NetFabric.Reflection NuGet 包中提供了更新和測試版本。 檢查其存儲庫以獲取源代碼。
這是canton7 的優秀解決方案的一個稍微擴展的版本,考慮到有關處理枚舉器的注釋:
public static Expression ForEach(Expression enumerable, ParameterExpression loopVar, Expression loopContent)
{
var elementType = loopVar.Type;
var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
var getEnumeratorCall = Expression.Call(enumerable, enumerableType.GetMethod("GetEnumerator"));
var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
var enumeratorDispose = Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose"));
// The MoveNext method's actually on IEnumerator, not IEnumerator<T>
var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
var breakLabel = Expression.Label("LoopBreak");
var trueConstant = Expression.Constant(true);
var loop =
Expression.Loop(
Expression.IfThenElse(
Expression.Equal(moveNextCall, trueConstant),
Expression.Block(
new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
loopContent),
Expression.Break(breakLabel)),
breakLabel);
var tryFinally =
Expression.TryFinally(
loop,
enumeratorDispose);
var body =
Expression.Block(
new[] { enumeratorVar },
enumeratorAssign,
tryFinally);
return body;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.