Ошибка "Недопустимый тип владельца для DynamicMethod" при сортировке интерфейса

Мы используем класс BindingListView<T> Эндрю Дэйви через sourceforge, чтобы привязать коллекции к DataGridView и разрешить сортировку и фильтрацию.

Это нормально для обычных коллекций. Однако в одном случае коллекция, к которой мы привязана, является интерфейсом, и мы получаем эту ошибку, если попытаемся ее отсортировать:

Invalid type owner for DynamicMethod

Ошибка в коде Эндрю Дэвиса, поэтому нам трудно знать, с чего начать.

        private static Comparison<T> BuildValueTypeComparison(PropertyInfo pi, ListSortDirection direction)
        {
            MethodInfo getMethod = pi.GetGetMethod();
            Debug.Assert(getMethod != null);


            DynamicMethod dm = new DynamicMethod("Get" + pi.Name, typeof(int), new Type[] { typeof(T), typeof(T) }, typeof(T), true);
            //^^^ ======== Here the line reporting the error=========== ^^^

            ILGenerator il = dm.GetILGenerator();

            // Get the value of the first object property.
            il.Emit(OpCodes.Ldarg_0);
            il.EmitCall(OpCodes.Call, getMethod, null);
            // Box the value type
            il.Emit(OpCodes.Box, pi.PropertyType);

            // Get the value of the second object property.
            il.Emit(OpCodes.Ldarg_1);
            il.EmitCall(OpCodes.Call, getMethod, null);
            // Box the value type
            il.Emit(OpCodes.Box, pi.PropertyType);

            // Cast the first value to IComparable and call CompareTo,
            // passing the second value as the argument.
            il.Emit(OpCodes.Castclass, typeof(IComparable));
            il.EmitCall(OpCodes.Call, typeof(IComparable).GetMethod("CompareTo"), null);

            // If descending then multiply comparison result by -1
            // to reverse the ordering.
            if (direction == ListSortDirection.Descending)
            {
                il.Emit(OpCodes.Ldc_I4_M1);
                il.Emit(OpCodes.Mul);
            }

            // Return the result of the comparison.
            il.Emit(OpCodes.Ret);

            // Create the delegate pointing at the dynamic method.
            return (Comparison<T>)dm.CreateDelegate(typeof(Comparison<T>));
        }

Ответ 1

UPDATE: Я, наконец, получил правильную работу, см. ниже.

DynamicMethod динамически создает метод во время выполнения; в библиотеке, которую вы используете, созданный метод добавляется к объекту T; однако, когда T является интерфейсом, это терпит неудачу, потому что вы не можете добавить метод к интерфейсу.

Основная проблема заключается в методе:

private static GetPropertyDelegate BuildGetPropertyMethod(PropertyInfo pi)

как это было написано, он будет работать только тогда, когда конкретный тип коллекции T.

Если вы измените реализацию на:

private static GetPropertyDelegate BuildGetPropertyMethod(PropertyInfo pi)
{
    MethodInfo getMethod = pi.GetGetMethod();
    Debug.Assert(getMethod != null);

    DynamicMethod dm = new DynamicMethod(
        "GetProperty_" + typeof(T).Name + "_" + pi.Name, typeof(object), 
        new Type[] { typeof(T) },
        pi.Module, 
        true);

    ILGenerator il = dm.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Callvirt, getMethod, null);
    if (pi.PropertyType.IsValueType)
    {
        il.Emit(OpCodes.Box, pi.PropertyType);
    }

    // Return the result of the comparison.
    il.Emit(OpCodes.Ret);

    return (GetPropertyDelegate)dm.CreateDelegate(typeof(GetPropertyDelegate));
}

он будет работать как для конкретных типов, так и для интерфейсов.

Вам также необходимо обновить следующие два метода:

private static Comparison<T> BuildValueTypeComparison(PropertyInfo pi, ListSortDirection direction)

private static Comparison<T> BuildNullableComparison(PropertyInfo pi, ListSortDirection direction)

Возможно, я ошибаюсь, но я думаю, что увеличение скорости, достигнутое в этих методах, происходит из быстрого считывания свойств, поэтому на самом деле не так много пользы от написания всех методов с использованием метода DynamicMethod; мы можем просто повторно использовать BuildGetPropertyMethod сверху. Таким образом, они становятся:

private static Comparison<T> BuildValueTypeComparison(
    PropertyInfo pi, 
    ListSortDirection direction)
{
    GetPropertyDelegate m = BuildGetPropertyMethod(pi);
    Comparison<T> d = delegate(T x, T y)
    {
        object mx = m(x);
        object my = m(y);

        IComparable c = (IComparable)mx;

        if (direction == ListSortDirection.Descending)
        {
            return -c.CompareTo(my);
        }

        return c.CompareTo(my);
    };

    return d;
}

private static Comparison<T> BuildNullableComparison(
    PropertyInfo pi, 
    ListSortDirection direction)
{
    GetPropertyDelegate m = BuildGetPropertyMethod(pi);
    Comparison<T> d = delegate(T x, T y)
        {
            object mx = m(x);
            object my = m(y);

            IComparable c = (IComparable)mx;

            if (c == null)
            {
                c = (IComparable)my;

                if (c == null)
                {
                    return 0;
                }

                return direction == ListSortDirection.Descending 
                    ? c.CompareTo(mx) : -c.CompareTo(mx);
            }

            return direction == ListSortDirection.Descending 
                ? -c.CompareTo(my) : c.CompareTo(my);
        };

    return d;
}

Очевидно, сделайте некоторое тестирование на нем, но я уверен, что это то, что вы хотите, и должно быть примерно так же быстро, как и предыдущий код.

Ответ 2

Не сдавайтесь и не используйте DataSet! Вы направляетесь в правильном направлении! Теперь давайте сначала взглянем на подпись функции:

DynamicMethod(string name, 
              Type returnType, 
              Type[] parameterTypes, 
              Type owner, 
              bool skipVisibility)

Error is "Invalid type owner for DynamicMethod"

Сообщение об ошибке пытается сказать вам, Type owner - это не то, что ожидает функция. Он хочет тип класса. Вероятно, вы передаете тип интерфейса в владелец Тип. Невозможно создать динамические методы в интерфейсе.

Пример 1.Error

Возможно, вы используете Dependency Injection, и вы можете использовать интерфейс.

Однако этот код ударит Runtime Error.

var viewModelList = GetViewModels(); //returns an IList<IViewModel> <-- has i !!
var blv = new BindingListView<IViewModel>(viewModelList);

Пример 2.Working

Попробуйте изменить свой код на использование типа бетона.

Теперь этот код НЕ будет удалять Runtime Error

var viewModelList = GetViewModels(); //returns an IList<ViewModel> <-- has no i !!
var blv = new BindingListView<ViewModel>(viewModelList);

Затем ваша сортировка и фильтрация на DataGridView будет работать автоматически:)

ИЗМЕНИТЬ ---------------------------------------------

P.S. О попытке переделать код:

Если вы используете шаблон MVVM/MVP, подумайте о следующей логике. IList<IViewModel> должен оставаться на стороне "VM + P". Цель использования IViewModel заключается, главным образом, в том, что я хочу заменить его, скажем, MockingViewModel : IViewModel для модульного тестирования логики "VM + P".

Теперь BindingListView<ViewModel> должен действительно находиться на стороне "V", то есть YourView : System.Windows.Form { ... }. И он будет привязан к источнику привязки оттуда YourBindingSource.DataSource = blv; Так как я не буду тестировать WinForm, любая логика в нем я попытаюсь реорганизовать их в презентаторы и viewmodels и сохранить представление как можно более тонким. Итак, я бы просто использовал ViewModel в BindingListView, а не интерфейс IViewModel.

Итак, BindingListView<ConcreteViewModel>, естественно, будет иметь для меня смысл, что он не принимает модельный интерфейс.

Обратитесь к этому вопросу о проектировании MVVM MVP и модульном тестировании WinForm: Должен ли я выполнить модульное тестирование моего представления в MVP (или виртуальной машине) или как сохранить код в представлении до минимума?

Ответ 3

Почему они создают новый метод для свойств, почему? Не могли бы они просто использовать эту PropertyInfo и получить значение свойства? Если бы я делал это, у меня были бы интерфейсы, и они не ограничивали бы пользователей их использованием. Они создают такой же метод, который вызывает оригинальный метод "get", я не понимаю, в чем смысл этого.

 private static GetPropertyDelegate BuildGetPropertyMethod(PropertyInfo pi)
        {
            MethodInfo getMethod = pi.GetGetMethod();
            Debug.Assert(getMethod != null);

            DynamicMethod dm = new DynamicMethod("__blw_get_" + pi.Name, typeof(object), new Type[] { typeof(T) }, typeof(T), true);
            ILGenerator il = dm.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.EmitCall(OpCodes.Call, getMethod, null);

            // Return the result of the comparison.
            il.Emit(OpCodes.Ret);

            // Create the delegate pointing at the dynamic method.
            return (GetPropertyDelegate)dm.CreateDelegate(typeof(GetPropertyDelegate));
        }

Исправлено для сортировки:

            private static Comparison<T> BuildComparison(string propertyName, ListSortDirection direction)
        {
            PropertyInfo pi = typeof(T).GetProperty(propertyName);
            Debug.Assert(pi != null, string.Format("Property '{0}' is not a member of type '{1}'", propertyName, typeof(T).FullName));

            if (typeof(IComparable).IsAssignableFrom(pi.PropertyType))
            {
                if (pi.PropertyType.IsValueType)
                {
                    return BuildValueTypeComparison(pi, direction);
                }
                else
                {
                    //CHANGED!!!!!
                    //GetPropertyDelegate getProperty = BuildGetPropertyMethod(pi);
                    return delegate(T x, T y)
                    {
                        int result;
                        //CHANGED!!!!!
                        object value1 = pi.GetValue(x, null);// getProperty(x);
                        //CHANGED!!!!!
                        object value2 = pi.GetValue(y, null); //getProperty(y);
                        if (value1 != null && value2 != null)
                        {
                            result = (value1 as IComparable).CompareTo(value2);
                        }
                        else if (value1 == null && value2 != null)
                        {
                            result = -1;
                        }
                        else if (value1 != null && value2 == null)
                        {
                            result = 1;
                        }
                        else
                        {
                            result = 0;
                        }

                        if (direction == ListSortDirection.Descending)
                        {
                            result *= -1;
                        }
                        return result;
                    };
                }
            }
            else if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                var compare = typeof(Nullable).GetMethod("Compare", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(pi.PropertyType.GetGenericArguments()[0]);
                return delegate (T x, T y)
                {
                    return (int)compare.Invoke(x,new[] { pi.GetValue(x, null), pi.GetValue(y, null) } );
                };
                //return BuildNullableComparison(pi, direction);
            }
            else
            {
                return delegate(T o1, T o2)
                {
                    if (o1.Equals(o2))
                    {
                        return 0;
                    }
                    else
                    {
                        return o1.ToString().CompareTo(o2.ToString());
                    }
                };
            }
        }