C# Игра с обобщениями, рефлексией и выводом типа - PullRequest
0 голосов
/ 27 января 2020

Я провожу некоторые эксперименты с обобщенными c типами и изо всех сил пытаюсь достичь того, что ищу. Я на самом деле не уверен, что это возможно. Вот пример:

public class Request { }
public class RequestTest : Request { }

public class Response { }
public class ResponseTest : Response { }

public abstract class Base
{
    // some stuff
}

public abstract class WebMethodBase<TInput, TOutput> : Base
    where TInput : Request
    where TOutput : Response
{
    public abstract TOutput Execute();

    protected TInput Input { get; set; }
    protected TOutput Output { get; set; }

    public WebMethodBase(TInput input, TOutput output)
    {
        Input = input;
        Output = output;
    }
}

public class Test : WebMethodBase<RequestTest, ResponseTest>
{
    public override ResponseTest Execute()
    {
        // Do some treatment with the input
        Console.WriteLine("Test");
        return Output;
    }

    public Test(RequestTest input, ResponseTest output) : base(input, output) { }
}

public static class WebMethodBaseHelper
{
    public static WebMethodBase<TInput, TOutput> Create<TInput, TOutput>(TInput input, TOutput output)
        where TInput : Request
        where TOutput : Response    
    {
        Type baseType = typeof(WebMethodBase<TInput, TOutput>);
        Type childType = baseType.Assembly.GetTypes().Where((t) => baseType.IsAssignableFrom(t) && t.IsClass).FirstOrDefault();
        var constructor = childType.GetConstructor(new Type[] { typeof(TInput), typeof(TOutput) });
        return (WebMethodBase<TInput, TOutput>)constructor.Invoke(new Object[] {input, output});
}

class Program
{
    private static TOutput Execute<TInput, TOutput>(TInput input, TOutput output)
        where TInput : Request
        where TOutput : Response 
    {
        WebMethodBase<TInput, TOutput> webMethod = WebMethodBaseHelper.Create(input, output);
        return webMethod.Execute();
    }

    private static ResponseTest Run(RequestTest request)
    {
        return Execute(request, new ResponseTest());
    }

    static void main(string[] args)
    {
        ResponseTest result = Run(new RequestTest());
    }
}

Этот пример работает, но для тех, кто читает этот код, неясно, какая реализация WebMethodBase<> запускается при вызове метода Execute. Чего я хотел бы добиться, так это изменить метод Execute, чтобы он мог вызывать его таким образом внутри метода Run:

return Execute<Test>(request);

Поскольку класс Test наследует WebMethodBase<> Я предполагаю, что каким-то образом мы сможем извлечь обобщенные типы c и сделать целую функцию также обобщенной c, но я не совсем уверен, что это возможно. Я уже пробовал много разных способов, и это самая близкая реализация, которую мне удалось получить из того, что я хочу.

Любая помощь для достижения этого или объяснение того, почему это невозможно, будет высоко ценится.

Спасибо

Редактировать

Я могу получить то, что хочу, имея следующее:

private static TOutput Execute<TMethod, TInput, TOutput>(TInput input)
    where TMethod : WebMethodBase<TInput, TOutput>
    where TInput : Request
    where TOutput : Response, new()
{
    TOutput output = new TOutput();
    var constructor = typeof(TMethod).GetConstructor(new Type[] { typeof(TInput), typeof(TOutput) });
    TMethod instance = (TMethod)constructor.Invoke(new Object[] { input, output });
    return instance.Execute();
}

И называя это так:

return Execute<Test, RequestTest, ResponseTest>(request);

Но я не хочу указывать все три типа каждый раз, когда мне нужно вызвать метод Execute.

1 Ответ

0 голосов
/ 30 января 2020

Я наконец нашел решение своей проблемы. Я действительно не знал о концепции ковариации , и это помогло мне достичь того, что я искал. Ниже приведены изменения, внесенные в образец, который я дал в исходном вопросе.

Добавлен ковариантный шаблон c интерфейса

public interface IWebMethodBase<out T1, out T2>
    where T1 : Request
    where T2 : Response
{
    T2 Execute();
}

Затем изменили класс WebMethodBase<> для реализации этого интерфейса

public abstract class WebMethodBase<TInput, TOutput> : Base, IWebMethodBase<TInput, TOutput>
    where TInput : Request
    where TOutput : Response
{
    public abstract TOutput Execute();

    protected TInput Input { get; set; }

    public WebMethodBase(TInput input)
    {
        Input = input;
    }
}

Также изменен класс Test, который наследует вышеприведенный класс, чтобы отразить его изменения и, наконец, метод Execute

public static Response Execute<T>(Request request)
{
    ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { request.GetType() });
    T instance = (T)constructor.Invoke(new Object[] { request });
    return instance.Execute();
}

И вызывать его

ResponseTest result = Execute<Test>(new RequestTest()) as ResponseTest;

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

Единственное, что я пытался улучшить есть необходимое приведение от последнего вызывающего, но я не нашел способа достичь этого, по-видимому, сам компилятор отказывается неявно приводить возвращаемое значение к его производной реализации. Я знаю, что это возможно в VB.Net с использованием option strict off, что я не рекомендую, но это все.

Если есть кто-то, у кого есть лучшее решение или улучшения для меня, не стесняйтесь редактировать этот ответ.

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