包含泛型字段比较的 C# lambda 表达式出错 - Unity/Mono

Error with C# lambda expression which includes generic type field comparison - Unity/Mono

我正在尝试实现一个可扩展的 class 来访问我的 Unity 游戏中的数据库。模型将在整个游戏中使用,可以是"fetched"。此方法是 Model<T> 基础 class 的一部分,其中 T 是与数据库匹配的架构类型。
因为我可以访问 T,所以我可以使用 lambda 查询架构。

不幸的是,当我尝试此操作并构建 lambda 表达式时,我 运行 陷入 运行 时间错误:

ArgumentException: The field handle and the type handle are incompatible.
System.Reflection.FieldInfo.GetFieldFromHandle (RuntimeFieldHandle handle, RuntimeTypeHandle declaringType) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/FieldInfo.cs:171)
ExpressionObject`1[IDObject].expression (Int32 id) (at Assets/Scripts/ELB/Test/ExistingDBScript.cs:24)
ExistingDBScript.Start () (at Assets/Scripts/ELB/Test/ExistingDBScript.cs:35)

我已经排除了数据库连接器本身的问题,并提出了最少的代码来重现该问题:

定义:

public class IDObject {
    public int id;
}

public class ExpressionObject<T> where T : IDObject {

    public void expression() {
        Expression<Func<T, bool>> expr = x => x.id == 0;
    }

    public void expression(int id) {
        Expression<Func<T, bool>> expr = x => x.id == id;
    }
}

实例:

ExpressionObject<IDObject> example = new ExpressionObject<IDObject>();
example.expression();
example.expression(2352);

对无参数表达式函数的第一次调用执行良好并且没有错误,但是第二次调用抛出上述错误。

我也试过在访问 id 时将 x 包装在一个函数中,这导致了同样的问题。

int idGetter(T ss) {
    return ss.id;
}

public void expression(int id) {
    Expression<Func<T, bool>> expr = x => idGetter(x) == id;
}

最后,我调用了 Func<T, bool> 而不是 Expression<Func<T, bool>>,这没有导致任何错误。不幸的是,我需要一个 Expression 来传递到我使用的数据库库,它也没有解决原来的问题。

有人有什么想法吗?

编辑:修正错别字

编辑:当表达式函数具有泛型类型时,这实际上有效。

public void expression<S>(int id) where S : IDObject {
    Expression<Func<S, bool>> expr = x => x.id == id;
}

example.expression<IDObject>(2352);

这实际上和我原来的一模一样,被重构了。我不希望 something() 的调用者关心 class 正在使用的 <T>

这是一个可以复制到unity中的例子。它与提供的示例非常相似。只需将它拖到某物上即可播放。

using UnityEngine;
using System.Linq.Expressions;
using System;

public class IDObject {
    public int id;
}

public class ExpressionObject<T> where T : IDObject {

    public void expression() {
        Expression<Func<T, bool>> expr = x => x.id == 0;
    }

    public void expression<S>(int id) where S : IDObject {
        Expression<Func<S, bool>> expr = x => x.id == id;
    }

    public void expression(int id) {
        Expression<Func<T, bool>> expr = x => x.id == id;
    }

}

public class ExtendedExpression : ExpressionObject<IDObject> {

    // This is the workaround
    public void expressionExt(int id) {
        expression<IDObject>(id);
    }

}


public class ExistingDBScript : MonoBehaviour {
    // Use this for initialization
    void Start () {

        ExtendedExpression example = new ExtendedExpression();
        // This works
        example.expression();
        // This also works
        example.expressionExt(2352);

        ExpressionObject<IDObject> example2 = new ExpressionObject<IDObject>();
        // This works
        example2.expression();
        // This does not work
        example2.expression(2352);
    }
}

在你的第二个代码块中:

public void expression(int id) {
    Expression<Func<T, bool>> expr = x => x.id == id;
}

总是会失败,因为 Tint 之间不可能进行相等比较,现在可能有 syntactic sugar 允许您编写它,即使它会在运行时失败.

要删除对 int 的依赖,您可以使用通用谓词:

public static class Extensions
{
    // checks if a property equals something, generic way
    public static bool IsEqualTo<T, TValue>(this T source, Expression<Func<T, TValue>> expression,
        Predicate<TValue> predicate)
    {
        if (expression == null) throw new ArgumentNullException("expression");
        if (predicate == null) throw new ArgumentNullException("predicate");

        var memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
            throw new ArgumentOutOfRangeException("expression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null)
            throw new ArgumentOutOfRangeException("expression");

        var value = propertyInfo.GetValue(source);
        var value1 = (TValue) value;
        var b = predicate(value1);
        return b;
    }
}

internal class Demo
{
    private static void Test()
    {
        var data = new MyData();

        var b1 = data.IsEqualTo(s => s.Valid, t => t == false);
        var b2 = data.IsEqualTo(s => s.Number, t => t == 1234);
    }

    private struct MyData
    {
        public bool Valid { get; set; }
        public int Number { get; set; }
    }
}

这只是一个用于读取和比较的示例,可以扩充以写入值等...

这似乎是旧版本 Mono 中的错误。更新到 Unity 测试版 (5.5 b7) 可以解决此问题。