I'm trying to get an out object value from an expression with delegate, but it return null. My code is
internal class TableInfoExtractor
{
private TableInfoExtractor()
{
}
public static TableInfoExtractor Extractor { get; } = new TableInfoExtractor();
public bool TryGetTableIndex(ITableInfoCache tableInfoCache, Type userDataType, out object tableInfo, out string error)
{
tableInfo = null;
error = null;
var t = tableInfoCache.GetType();
var mm = t.GenericTypeArguments[0];
var tableInfoCacheType = typeof(TableInfoCache<>).MakeGenericType(mm);
try
{
//internal bool TryGetTableInfo<TUserData>(out TableInfo<TDataBaseProvider, TUserData> tableInfo, out string error)
var method = t.GetMethod("TryGetTableInfo", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(userDataType);
var p1 = Expression.Parameter(typeof(ITableInfoCache)); //TryGetTableInfoCall->ITableInfoCache tableInfoCache
var tableInfoCacheExp = Expression.Convert(p1, tableInfoCacheType);
var objType = typeof(object).MakeByRefType();//TryGetTableInfoCall->out object tableInfo
var p2 = Expression.Parameter(objType, "tableInfo");
var tableInfoType = typeof(TableInfo<,>).MakeGenericType(mm, userDataType);
var tableInfoExp = Expression.Convert(p2, tableInfoType);
var errorExp = Expression.Parameter(typeof(string).MakeByRefType());//TryGetTableInfoCall->out string error
var callExp = Expression.Call(tableInfoCacheExp, method, tableInfoExp, errorExp);
var fun = Expression.Lambda<TryGetTableInfoCall>(callExp, p1, p2, errorExp).Compile();
var ret = fun.Invoke(tableInfoCache, out tableInfo, out error); <-- here , ret=>true; tableInfo => null (this is wrong, it should have a value)
return ret;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
throw new NotImplementedException();
}
}
as the code point, fun.Invoke(tableInfoCache, out tableInfo, out error);
invoked success . If pressed F11, VS can step into method TryGetTableInfo
. and the method runs ok, the out parameter tableInfo has value. But when step out the method, tableInfo will be null. i don't know why, and how do i fix it.
the other relation code
internal class TableInfoCache<TDataBaseProvider> : ITableInfoCache where TDataBaseProvider : class
{
internal bool TryGetTableInfo<TUserData>(out TableInfo<TDataBaseProvider, TUserData> tableInfo, out string error) where TUserData : class
{
error = null;
tableInfo =
return true;
}
}
internal class TableInfo<TDataBaseProvider, TUserData> where TDataBaseProvider : class where TUserData : class
{
}
internal interface ITableInfoCache
{
}
internal class UserInfo
{
}
internal class DataBaseProvider
{
}
internal delegate bool TryGetTableInfoCall(ITableInfoCache tableInfoCache, out object tableInfo, out string error);
Call the method
var tableInfoCache = new TableInfoCache<DataBaseProvider>();
TableInfoExtractor.Extractor.TryGetTableIndex(tableInfoCache, typeof(UserInfo), out var tableInfo, out error);
online demo https://dotnetfiddle.net/AWpQfM
I haven't looked at the disassembly but I think the result of the Convert
expression is declaring silently a local variable that is passed to TryGetTableInfo
. It is assigned to there, but the parameter it was converted from is left unchanged - the one we actually get back.
So, a solution would be to make this variable assignment explicit and after the Expression.Call
, assign back to the original parameter:
var errorExp = Expression.Parameter(typeof(string).MakeByRefType()); //TryGetTableInfoCall->out string error
var innerOutVariable =
Expression.Variable(tableInfoType);
var callMethod = Expression.Call(tableInfoCacheExp,
method,
innerOutVariable, errorExp);
var innerOutToOuterOutVariable = Expression.Assign(p2, innerOutVariable);
var returnVariable = Expression.Variable(method.ReturnType);
var assignToReturnVariable = Expression.Assign(returnVariable, callMethod);
var block = Expression.Block(new List<ParameterExpression> {
innerOutVariable,
returnVariable},
assignToReturnVariable,
innerOutToOuterOutVariable,
returnVariable);
var fun = Expression.Lambda<TryGetTableInfoCall>(
block, p1, p2, errorExp);
var ret = fun.Compile().Invoke(tableInfoCache,
out tableInfo, out error);