Как создать экземпляр Lazy <Square>, используя информацию о типе среды выполнения и функцию, которая только возвращает Shape? - PullRequest
0 голосов
/ 23 апреля 2020

Я хочу создать Lazy<> с информацией времени выполнения о содержащемся типе, но я не уверен, как создать требуемый инициализатор Func<>. Я чувствую, что ответ где-то в Delegate.CreateDelegate, но я не мог понять, как заставить это работать. Ниже показано, что я хочу сделать:

class ShapeTools {
    abstract class Shape {}
    class Square : Shape {}

    Lazy<Square> aLazyShape;

    ShapeTools() {
        setup(GetType().GetFields().Where(f => f.Name == "aLazyShape").First());
    }

    // returns a shape matching the provided type (unimplemented)
    Shape GetShape(Type shapeType) { return null; }

    void setup (FieldInfo field) { // aLazyShape
        var funcType = typeof(Func<>).MakeGenericType(field.FieldType); // = typeof(Func<Square>)
        var shapeType = funcType.GetGenericArguments().First(); // = typeof(Square)
        // var myFunc = Activator.CreateInstance(funcType, () => { return GetShape(shapeType); }) // doesn't compile - type doesn't match

        var lazy = Activator.CreateInstance(field.FieldType, myFunc); // This takes a Func<Square>
        field.SetValue(this, lazy);
    }
}

Ответы [ 2 ]

1 голос
/ 23 апреля 2020

Вы можете достичь того, что ищете, используя System.Linq.Expressions. Почему вы хотите сделать это таким образом, это другое дело. =)

См. Этот другой ответ о том, откуда взялись ключевые детали использования Expression.

Обратите внимание, что ваш код для создания var funcType не возвращался typeof(Func<Square>), но вместо typeof(Func<Lazy<Square>>); Я исправил это. Большинство вещей было сделано public для удобства при компиляции. Вы можете изменить доступ к GetShape, если хотите обновить BindingFlags, например, при вызове GetMethod.

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SomeNamespace
{
    class Program
    {
        static void Main(string[] args)
        {

            ShapeTools st = new ShapeTools();
            ShapeTools.Square s = st.aLazyShape.Value;
            // s will be whatever ShapeTools.GetShape returns
        }

        public class ShapeTools
        {
            public abstract class Shape { }
            public class Square : Shape { }

            public Lazy<Square> aLazyShape;

            public ShapeTools()
            {
                setup(GetType().GetFields().Where(f => f.Name == "aLazyShape").First());
            }

            // returns a shape matching the provided type (unimplemented, just an example)
            public static object GetShape(Type shapeType) { return new Square(); }

            void setup(FieldInfo field)
            { // only handles 'aLazyShape' right now
                Type funcType = typeof(Func<>).MakeGenericType(field.FieldType.GenericTypeArguments[0]); // = typeof(Func<Square>)
                Type shapeType = funcType.GetGenericArguments().First(); // = typeof(Square)

                // get MethodInfo for the static method in this class that returns the right shape
                MethodInfo getInstanceOfType = GetType().GetMethod(nameof(GetShape));

                // build the Func dynamically
                var typeConst = Expression.Constant(shapeType); // get the shapeType as an Expression
                var callGetInstance = Expression.Call(getInstanceOfType, typeConst); // invoke our (static) method to get the instance of shape
                var cast = Expression.Convert(callGetInstance, shapeType); // cast the return of our method (which is object) to the right type
                var toLambda = Expression.Lambda(cast); // wrap everything in a Lambda to return our instance
                var finalFunc = toLambda.Compile(); // compile to the final Func

                var lazy = Activator.CreateInstance(field.FieldType, finalFunc); // now create the Lazy<T>, where T is Square
                field.SetValue(this, lazy);
            }
        }
    }
}

Наконец, обратите внимание, что GetShape был сделан stati c. Это было для удобства при использовании Expressions - если вы хотите, вы можете вместо этого передать экземпляр ShapeTools в код Expressions.

И, как написано, ShapeTools.setup является просто примером того, как это может работать. Я предполагаю, что вы захотите очистить его для обработки полей других типов, кроме Lazy<Shape>.

0 голосов
/ 23 апреля 2020

// var myFunc = Activator.CreateInstance(funcType, () => { return GetShape(shapeType); }) // doesn't compile - type doesn't match

C# является языком со статической типизацией, и поэтому каждый тип должен быть полностью известен во время компиляции. Как вы обнаружили, в то время как вы можете попросить среду выполнения создать для вас любой тип, для которого вы строите определение (с Type объектом), тип возвращаемого значения должен быть полностью известен - функция, которую вы вызываете, возвращает object для этого самого Причина.

У вас есть несколько вариантов здесь:

  1. Оставайтесь на уровне объекта и используйте дополнительные вызовы отражения, чтобы использовать ваш вновь созданный объект. Конечно, большой удар по производительности.

  2. Go dynamic, который создает сайт компилятора для перевода любых вызовов через него. Он также распространяется как чума, все, что использует dynamic, возвращает dynamic, поэтому, пока вы не получите что-то, вы будете знать, какой тип вызовов вы будете использовать. Амортизированный умеренный удар по производительности, очень высокий удар по производительности во время первой компиляции JIT.

  3. Используйте интерфейсы. У вас уже есть базовый класс, если вы определите его достаточно умно, это все, что вам нужно для использования вашего объекта. Эта функция myfunc будет иметь тип Func<Shape> в этом случае, и она, очевидно, может получить доступ только к части Shape вашего объекта, хотя вы можете попробовать использовать приведение с использованием is / as по мере необходимости, обычно в if с. Удар очень низкой производительности, но вы должны разработать свой умный шрифт (который вы должны делать в любом случае).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...