Упростить общий тип вывода

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

public static TResult LoadFromAnySource<TContract, TSection, TResult>
    (this TSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TSection : ConfigurationSection
    where TResult : IDatabaseConfigurable<TContract, TSection>, new() 
    where TContract : new()

Но это перебор: когда я прохожу TResult, я уже знаю, что именно есть TContract и TSection. В моем примере:

public interface ISourceObserverConfiguration 
    : IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>

Но я должен написать следующее:

sourceObserverSection.LoadFromAnySource<SourceObserverContract, 
                                        SourceObserverSection, 
                                        SourceObserverConfiguration>
    (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

Вы можете видеть, что я должен указать пару <SourceObserverContract, SourceObserverSection> дважды, это нарушение DRY принципа. Поэтому я хотел бы написать что-то вроде:

sourceObserverSection.LoadFromAnySource<SourceObserverConfiguration>
   (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

и сделать SourceObserverContract и SourceObserverSection вывод из интерфейса.

Возможно ли это в С#, или я должен указывать его везде вручную?

IDatabaseConfigurable выглядит следующим образом:

public interface IDatabaseConfigurable<in TContract, in TSection> 
    where TContract : ConfigContract
    where TSection : ConfigurationSection
{
    string RemoteName { get; }

    void LoadFromContract(TContract contract);

    void LoadFromSection(TSection section);
}

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

Ответ 1

Нет, вы не можете. Вывод типа не учитывает возвращаемый тип метода. TResult может содержать всю необходимую информацию, но вывод типа не будет использовать ее.

Вам нужно будет сделать часть TContract подписи метода, чтобы тип был выведен. TResult является избыточным, нет необходимости, чтобы он был общим, просто используйте IDataBaseConfigurable<TContract, TSection> в качестве возвращаемого типа метода.

Ответ 2

С текущей сигнатурой метода метода LoadFromAnySource это невозможно сделать так, как вам хотелось бы. Однако это можно сделать с модификацией подписи LoadFromAnySource.

Поскольку вы уже знаете интерфейс ISourceObserverConfiguration (и из этого мы знаем, что он повторно реализует интерфейс IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>), используйте это как общее ограничение вместо этого в объявлении метода:

Вместо

public static TResult LoadFromAnySource<TContract, TSection, TResult>
    (this TSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TSection : ConfigurationSection
    where TResult : IDatabaseConfigurable<TContract, TSection>, new() 
    where TContract : new()

Используйте этот

public static TResult LoadFromAnySource<TResult>
    (this SourceObserverSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TResult : ISourceObserverConfiguration, new()

Это устраняет необходимость в TContract и TSection, как они известны в интерфейсе ISourceObserverConfiguration. Компилятор знает, что ограничение интерфейса IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>, и оно будет работать.

Кроме того, поскольку это метод расширения и мы определяем общее ограничение на ISourceObserverConfiguration, нам нужно расширить SourceObserverSection.


Тогда вы можете потреблять его точно так, как хотите:
sourceObserverSection.LoadFromAnySource<SourceObserverConfiguration>
   (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

Обновление

Основываясь на модификациях/разъяснениях OP на вопрос, у меня есть следующее:

Возможно ли это в С#, или я должен указывать его везде вручную?

Вы должны указать его вручную. не, чтобы сделать это на основе требования повторной реализации, где базовый интерфейс определяет конкретный тип, который пытается устранить ограничение верхнего уровня. Другими словами, поскольку у вас есть несколько реализаций IDatabaseConfigurable, вызывающий должен указать, какую реализацию использовать с помощью ограничений TContract и TSection.

Ответ 3

Это зависит от того, насколько гибким является ваш код и что вы с ним делаете. В общем, нет - вам либо нужно указать все общие типы, либо ни один из них.

Это означает, что просто передача TResult не означает, что другие общие типы разрешены (хотя логически они могут быть).

В зависимости от того, насколько вы можете изменить свои определения, вы можете сделать немного более аккуратным:

public static class Helper
{
    public static TResult LoadFromAnySource<TResult>(this ConfigurationSection section, string serviceBaseUri, string nodeName)
        where TResult : IDatabaseConfigurable<object, ConfigurationSection>, new()
    {
        return default(TResult);
    }
}

public class ConfigurationSection { }
public interface IDatabaseConfigurable<out TContract, out TSection> 
    where TContract : new()
    where TSection : ConfigurationSection
{ 
}

public class DatabaseConfigurable<TContract, TSection> : IDatabaseConfigurable<TContract, TSection>
    where TContract : new()
    where TSection : ConfigurationSection
{ 
}

public class SourceObserverContract { }
public class SourceObserverSection : ConfigurationSection { } 

Что позволяет писать:

var sect = new ConfigurationSection();
sect.LoadFromAnySource<DatabaseConfigurable<SourceObserverContract, SourceObserverSection>>("a", "B");

Разница заключается в том, что вы помещаете ограничение на IDatabaseConfigurable, а не на метод. Вы также должны сделать интерфейс ковариантным. Если это невозможно с вашим дизайном, то, насколько я вижу, невозможно сделать то, что вы пытаетесь выполнить (без наличия не общего IDatabaseConfigurable)