Search code examples
c#expression-trees

When trying to convert a method to expression tree, got an error 'System.Void' cannot be used for return type ''


I'm trying to convert a method to an expression tree for a test, but I got an error

Exception thrown: 'System.ArgumentException' in System.Linq.Expressions.dll Expression of type 'System.Void' cannot be used for return type 'TestDatabase.Models.TestEnum'

and the same time, I want to know how to get the input value for output as the exception test.

The original method I'm trying to recreate as an expression tree is:

private TEnum ConvertStringToEnum(TDbValueType dbValue)
{
    if (Enum.IsDefined(typeof(TEnum), dbValue))
    {
        return (TEnum)Enum.Parse(typeof(TEnum), (string)(object)dbValue);
    }

    throw new Exception("");
}

and the expression tree build method is

public static Func<TString, TEnum> BuildStringToEnumConvertor<TString, TEnum>()
{
    var enumType = typeof(TEnum);
    var enumTypeExp = Expression.Constant(enumType);

    //var labeleExp = Expression.Label(enumType);

    var dbValueExp = Expression.Parameter(typeof(TString), "dbValue");
    var m = typeof(Enum).GetMethod("IsDefined", [typeof(Type), typeof(object)]);

    //test
    var testExp = Expression.Call(null, m, enumTypeExp, dbValueExp);

    //true
    var objExp = Expression.Convert(dbValueExp, typeof(object));
    var strExp = Expression.Convert(objExp, typeof(string));

    m = typeof(Enum).GetMethod("Parse", [typeof(Type), typeof(string)]);
    var retExp = Expression.Call(null, m, enumTypeExp, strExp);
    objExp = Expression.Convert(retExp, enumType);

    //var returnExp = Expression.Return(labeleExp, objExp);

    //false
    //var x = Expression.Lambda<Func<string>>(dbValueExp).CompileFast();
    //var x = Expression.Lambda<Func<string>>(dbValueExp).Compile();

    var falseExp = Expression.Throw(Expression.Constant(new Exception($"could not convert \" {{how to get the input parameter value ??}} \" to type \"{typeof(TEnum).Name}\".")));

    var xxExp = Expression.IfThenElse(testExp, objExp, falseExp);

    //var block = Expression.Block(objExp, falseExp);

    //return Expression.Lambda<Func<TString, TEnum>>(block, dbValueExp).CompileFast();
    return Expression.Lambda<Func<TString, TEnum>>(xxExp, dbValueExp).Compile(); <-- error here 

}


internal enum TestEnum
{
    Age = 1,
    Name,
    Address,
    CellPhone,
    Max = 500
}

When I call var func = BuildStringToEnumConvertor<string, TestEnum>(); I get the following exception:

Unhandled exception. System.ArgumentException: Expression of type 'System.Void' cannot be used for return type 'Program+TestEnum'
   at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
   at Program.BuildStringToEnumConvertor[TString,TEnum]()

Running demo here: https://dotnetfiddle.net/IkYEpJ


Solution

  • The type returned by Expression.IfThenElse is always void, to specify a type, use Expression.Condition:

    Expression.Condition(testExp, objExp, falseExp, enumType);
    

    You also need to specify a type for the throw expression:

    Expression.Throw(Expression.Constant(new Exception("")), enumType);
    

    To get the input parameter value, you can manually construct the exception object

    // string.Concat(string, string, string)
    var concat = typeof(string).GetMethod("Concat", [typeof(string),
                                                     typeof(string),
                                                     typeof(string)]);
    
    // Concat constant strings with the parameter
    var msg = Expression.Call(concat,
                    Expression.Constant(@"could not convert """),
                    strExp,
                    Expression.Constant($@""" to type ""{typeof(TEnum).Name}""."))
    
    // Create an exception object with the error message
    var ex = Expression.New(typeof(Exception).GetConstructor([typeof(string)]), msg);
    
    // Emit throw statement
    var falseExp = Expression.Throw(ex, enumType);