简体   繁体   中英

Using reflection to instantiate an IEnumerable<MyType> where MyType has a generic parameter

I've built a simple extensible computation framework, where each class represents a different function for the framework.

This is a quick example of what I did:

BaseFunction :

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 result;
        }

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

SampleFunction :

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;
        }
    }
}

Using reflection, the framework also provided a CLI where the user can select its function and run it.

This is how all the functions are retrieved:

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);
}

and this is how the CLI is built:

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

The framework works fine, but, as you can see, everything is a long now, and this takes us to my issue: I'd like to make the BaseFunction class generic, so that I can have different functions returning different type of values.

However, changing BaseFunction to BaseFunction<TResult> breaks the Functions method as I can't return a IEnumerable<BaseFunction> .

The logical next step is to add an interface, make BaseFunction implement the interface and add the generics to BaseFunction . This means that Functions can now return a IEnumerable<IBaseFunction> .

What still doesn't work, however, is the way I build the CLI menu: my interface must have the Execute method, and so we're back to square one: I can't add that method to my interface, because the return type is a generic and the interface doesn't have the generic reference.

Here I'm kind of stuck.

Is there any way to make this kind of framework work without changing all my return types to object (or maybe struct ?) considering that I may also need to return non-numeric types?

Assuming that input and result can be anything, you need something like this:

    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;
    }

This still allows you to combine functions into IEnumerable<BaseFunction> , execute them, but also allows to work with strongly-typed input and result, when implementing particular function.

(I've modified BaseFunction a little to throw away out parameter)

If you change BaseFunction to BaseFunction<TResult>

    public abstract class BaseFunction<TResult>

Why not then just change the signature of Functions to return BaseFunction <TResult>?

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

Update:

Extracting a base interface to find commonality, then setting up TResult in the abstract class seems to work. I also refined the linq query a bit.

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("\n\nPress 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 && 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 result;
    }

    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;
    }
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM