2016-05-06 4 views
10

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

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 выведенного из интерфейса.

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

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

Если ваша сигнатура метода была 'IDatabaseConfigurable LoadFromAnySource (этот раздел TSection, строка serviceBaseUri, string nodeName, Func contractCreator) '(или просто' TContract contract'), типы могут быть выведены из использования. –

+1

Как это нарушение принципа DRY? Это немного сложнее, чем «не писать одно и то же дважды». Вам, наверное, все равно, что вы три раза пишете 't' в' twitter', не так ли? : D Эти два не являются * одинаковыми - одно более общее, чем другое. В любом случае комментарий @JeroenMostert - это самый лучший подход, и он должен быть полностью ответом. Хотя, вероятно, где-то вроде Programmers, а не SO - я не думаю, что это хороший вопрос для SO, действительно. – Luaan

+0

@JeroenMostert Да, но они не знали, для чего нужно использовать IDatabaseConfigurable. – Rob

ответ

2

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

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

+0

Вот обсуждение возможного решения: https://github.com/dotnet/roslyn/issues/5023 –

1

С текущей сигнатурой метода 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); 

Update

на основе модификаций OP в/пояснения к вопросу, я следующее:

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

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

+0

Это не скомпилируется, поскольку вы больше не определяете 'TSection'. – juharr

+0

Я обновил свой ответ и по достоинству оценю его, так как теперь он корректен –

+0

'ISourceObserverConfiguration' является одним из конкретных интерфейсов, у меня есть несколько интерфейсов, наследующих' IDatabaseConfigurable'. Поэтому, к сожалению, я не могу написать здесь «ISourceObserverConfiguration», он должен быть более общим. –

1

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

Это означает, что простое прохождение 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)

+0

Я знаю, но иногда его можно избежать с извлечением какого-то типа на уровне класса. Например, 'Foo.Bar (B b, C c)', когда мы можем заключить 'B' и' C', заставляем нас указывать все аргументы, но 'Foo .Bar (B b, C c)' может избежать этого , Но я не вижу, как я могу сделать тот же (или похожий) трюк здесь ... –

+0

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

Смежные вопросы