Использование отражения для создания экземпляра IEnumerable, где MyType имеет общий параметр

#c# #generics #reflection

#c# #общие #отражение

Вопрос:

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

Это краткий пример того, что я сделал:

Базовая функция:

 namespace MyNamespace
{
    public abstract class BaseFunction
    {
        public abstract string Name { get; }

        public abstract int Index { get; }

        public long Execute()
        {
            Execute(ReadInput() /* out of scope */, out long result);
            return resu<
        }

        internal abstract void Execute(string input, out long rResult);
    }
}
 

Функция выборки:

 namespace MyNamespace.Code
{
    public class SampleFunction: BaseFunction
    {
        public override string Name => "Sample Function";

        public override int Index => 1;

        internal override void Execute(string input, out long result)
        {
            result = 0;
        }
    }
}
 

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

Так извлекаются все функции:

 public static IEnumerable<BaseFunction> Functions()
{
    return GetTypesInNamespace(Assembly.GetExecutingAssembly(), "MyNamespace.Code")
        .Where(type => type.Name != "BaseFunction")
        .Select(type => (BaseFunction)Activator.CreateInstance(type))
        .OrderBy(type => type.Index);
}
 

и вот как создается CLI:

 var menu = new EasyConsole.Menu();
foreach (var day in FunctionsUtils.Functions())
{
    menu.Add(function.Name, () => function.Execute());
}
 

Фреймворк работает нормально, но, как вы можете видеть, все long теперь, и это подводит нас к моей проблеме: я хотел бы сделать BaseFunction класс универсальным, чтобы у меня могли быть разные функции, возвращающие значения разного типа.

Однако изменение BaseFunction на BaseFunction<TResult> прерывает Functions метод, поскольку я не могу вернуть a IEnumerable<BaseFunction> .

Следующий логический шаг — добавить интерфейс, BaseFunction реализовать интерфейс и добавить обобщения BaseFunction . Это означает, что Functions теперь может возвращать a IEnumerable<IBaseFunction> .

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

Здесь я как бы застрял.

Есть ли какой-либо способ заставить такую структуру работать без изменения всех моих возвращаемых типов на object (или, может struct быть?), Учитывая, что мне также может потребоваться возвращать нечисловые типы?

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

1. Что вы ожидаете, что вызывающий объект будет делать с неизвестным количеством функций, каждая из которых имеет неизвестное возвращаемое значение? Если вызывающий объект никогда не может знать, каково возвращаемое значение, как они могут сделать с ним что-нибудь значимое?

2. Как вы можете видеть здесь () => function.Execute() , вызывающий объект не использует возвращаемый тип, поэтому он не имеет к нему отношения.

3. Если вызывающий объект никогда не использует возвращаемый тип, то зачем вам это вообще нужно? Если они никогда не используют его, то им не нужно, чтобы он мог быть чем-то иным, кроме long .

Ответ №1:

Предполагая, что ввод и результат могут быть любыми, вам нужно что-то вроде этого:

     public abstract class BaseFunction
    {
        public abstract string Name { get; }

        public abstract int Index { get; }

        public object Execute() => Execute(ReadInput());

        private object ReadInput()
        {
            // out of scope
            return null;
        }

        protected abstract object Execute(object input);
    }

    public abstract class BaseFunction<TInput, TResult> : BaseFunction
    {
        protected sealed override object Execute(object input) => Execute(ConvertInput(input));

        protected abstract TInput ConvertInput(object input);

        protected abstract TResult Execute(TInput input);
    }

    public sealed class SampleFunction : BaseFunction<string, long>
    {
        public override string Name => "Returns string length";

        public override int Index => 0;

        protected override string ConvertInput(object input) => (string)input;

        protected override long Execute(string input) => input.Length;
    }
 

Это по-прежнему позволяет вам объединять функции IEnumerable<BaseFunction> , выполнять их, но также позволяет работать со строго типизированными вводом и результатом при реализации конкретной функции.

(Я немного изменил BaseFunction , чтобы выбросить параметр out)

Ответ №2:

Если вы измените базовую функцию на BaseFunction<TResult>

     public abstract class BaseFunction<TResult>
 

Почему бы тогда просто не изменить сигнатуру функций, чтобы возвращать BaseFunction <TResult> ?

 public static class FunctionClass<TResult>
{
    public static IEnumerable<BaseFunction<TResult>> Functions()
    {
 

Обновить:

Извлечение базового интерфейса для поиска общности, а затем настройка TResult в абстрактном классе, похоже, работает. Я также немного доработал запрос linq.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MyNamespace
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var list = FunctionClass.Functions();
        foreach(var item in list)
        {
            Console.WriteLine($"{item.Name} - {item.GetType().Name}");
            if(item is BaseFunction<int>)
            {
                Console.WriteLine($"int result {((BaseFunction<int>)item).Execute()}");
            }
            if (item is BaseFunction<long>)
            {
                Console.WriteLine($"long result {((BaseFunction<long>)item).Execute()}");
            }
        }
        Console.WriteLine("nnPress Any Key to Close");
        Console.ReadKey();
    }
}

public class FunctionClass
{

    private static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace)
    {
        return
          assembly.GetTypes()
                  .Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal))
                  .ToArray();
    }

    public static IEnumerable<IBaseFunction> Functions()
    {
        return GetTypesInNamespace(Assembly.GetExecutingAssembly(), "MyNamespace.Code")
            .Where(type => type.IsClass amp;amp; typeof(IBaseFunction).IsAssignableFrom(type))
            .Select(type => (IBaseFunction)Activator.CreateInstance(type))
            .OrderBy(type => ((IBaseFunction)type).Index);
    }
}
}

namespace MyNamespace
{
    public interface IBaseFunction
    {
        public string Name { get; }

        public long Index { get; }

    }
public abstract class BaseFunction<TResult> : IBaseFunction
{
    public virtual string Name { get; }

    public virtual long Index { get; }

    public TResult Execute()
    {
        Execute("foo" /* out of scope */, out TResult result);
        return resu<
    }

    internal abstract void Execute(string input, out TResult rResult);
}
}

namespace MyNamespace.Code
{
    public class SampleFunction : BaseFunction<int>
    {
        public override string Name => "Sample Function1 - with int";

        public override long Index => 1;

    internal override void Execute(string input, out int rResult)
    {
        rResult = 0;
    }
}

public class SampleFunction2 : BaseFunction<long>
{
    public override string Name => "Sample Function2 - with long";

    public override long Index => 1;

    internal override void Execute(string input, out long result)
    {
        result = 0;
    }
}
 

}

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

1. Потому Functions() что должен возвращать как a BaseFunction<int> , так и a BaseFunction<long> (просто в качестве примера). Этот метод, в идеале, должен возвращать все мои классы независимо от их TResult значения.

2. Что возвращает Execute в этих случаях? Возвращает ли он int в одном и long в другом?

3. Возможно, или даже string , если необходимо. Допустим, мне не нужно ничего, кроме примитивных типов.