#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
-> aMonoBehaviour
, который мы можем вставить и обработать.5. @Aybe добавил пример, который вы просили. Я использую unity 5.2.3f1, который не является последней версией. Я попробую использовать более новую версию и свяжусь с вами.
Ответ №2:
Это похоже на ошибку в старых версиях Mono. Обновление до бета-версии Unity (5.5 b7) устраняет проблему.