Ошибка с лямбда-выражением C #, которое включает сравнение полей универсального типа — Unity / Mono

#c# #linq #generics #lambda #mono

#c# #linq #общие #лямбда #mono

Вопрос:

Я пытаюсь реализовать расширяемый класс для доступа к базе данных в моей игре Unity. Модели будут использоваться на протяжении всей игры, которые могут быть «извлечены». Этот метод является частью базового класса Model<T> , где T — тип схемы, соответствующий базе данных.
Поскольку у меня есть доступ к T, я могу запрашивать схему с помощью 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);
 

Первый вызов функции выражения без параметров выполняется нормально и без ошибок, однако второй выдает ошибку, указанную выше.

Я также пытался обернуть x в функцию при доступе к идентификатору как таковому, что приводит к той же проблеме.

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

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

Наконец, я сделал вызов a 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() заботился о <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);
    }
}
 

Комментарии:

1. Чего именно вы пытаетесь достичь, прочитать или записать свойство или поле в одном из ваших классов, используя выражение (ваш пример немного вводит в заблуждение)?

2. Выражение будет использоваться для чтения таблицы «idObject» из базы данных. Когда я извлекаю, я хочу иметь возможность передавать идентификатор, и он вернет правильную строку (или технически строки), которая соответствует идентификатору. Я пробовал это с нестандартным типом (т.Е. idObject вместо T), и он отлично работает. Однако то, для чего оно используется, выходит за рамки вопроса. Я просто создаю экземпляр выражения, и он терпит неудачу. Я хочу знать, почему.

3. @Aybe, я отредактировал OP из-за опечатки, которая, возможно, вас смутила, а также добавил пример измененной, но рабочей версии. Это не то, что я хочу, но может выявить проблему, которую я, возможно, не заметил. В противном случае это похоже на ошибку mono…

4. Честно говоря, я не понимаю, чего вы пытаетесь достичь, потому что ваш вопрос не так ясен 🙂 Я думаю, вы ищете Predicate ответ, взгляните на мой ответ, посмотрите, дает ли он вам подсказку или исправляет ваш вопрос 🙂

5. Код в разделе Определения и экземпляр работает нормально для меня, хотя я пробовал использовать .NET, а не Mono. Возможно, это ошибка в Mono?

Ответ №1:

В вашем втором блоке кода:

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

Неизменно завершится неудачей, потому что между и не возможно сравнение на равенство T int , теперь может быть синтаксический сахар, позволяющий вам записать его, даже если он завершится с ошибкой во время выполнения.

Чтобы удалить эту зависимость 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; }
    }
}
 

Это всего лишь пример для чтения и сравнения, его можно дополнить для записи значения и т. Д…

Комментарии:

1. Проблема в том, что возможно сравнение между T(.id) и int .

2. (Я не должен нажимать enter раньше …) «Where» в классе означает, что T должен быть экземпляром idObject, который имеет идентификатор свойства типа int . Это означает, что любой T должен иметь идентификатор свойства и быть сопоставимым с любым другим целым числом. В случае, если я что-то упустил, не могли бы вы ответить на вопрос — почему исходный пример завершается ошибкой (тот, который вы процитировали), а новый пример, который я добавил (где общий определяется функцией, а не классом), проходит?

3. OP не сравнивает T и int . Сравнение выполняется между T.id и id , оба из которых имеют тип int , а предложение where в классе гарантирует, что T у него будет поле id типа int .

4. @AlasdairHurst Вставка первого примера в Unity здесь не вызывает ошибки (поскольку он не вычисляется). Является ли последняя версия в Unity? В вашей версии может быть ошибка, если это не так. Чтобы мы могли вам еще больше помочь, вы должны опубликовать a Short, Self Contained, Correct (Compilable), Example -> a MonoBehaviour , который мы можем вставить и обработать.

5. @Aybe добавил пример, который вы просили. Я использую unity 5.2.3f1, который не является последней версией. Я попробую использовать более новую версию и свяжусь с вами.

Ответ №2:

Это похоже на ошибку в старых версиях Mono. Обновление до бета-версии Unity (5.5 b7) устраняет проблему.