Search code examples
c#expression

Expression out parameter return null


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


Solution

  • 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);
    

    Your modified (working) fiddle