简化通用类型推断

时间:2016-05-06 12:31:50

标签: c# .net generics

我正在编写一个通用代码,该代码应该处理从多个源加载数据的情况。我有一个带有以下签名的方法:

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时,我已经知道TContractTSection到底是什么。在我的例子中:

public interface ISourceObserverConfiguration 
    : IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>

但我必须写下以下内容:

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

您可以看到我必须两次指定对<SourceObserverContract, SourceObserverSection>,这违反了 DRY 原则。所以我想写一些类似的东西:

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

并从界面推断SourceObserverContractSourceObserverSection

是否可以在 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);
}

然后扩展只是根据一些逻辑调用这两个方法。我必须指定类型,因为我需要访问每个特定实现的属性,所以我需要一个协方差。

3 个答案:

答案 0 :(得分:2)

不,你不能。类型推断不考虑方法的返回类型。 TContract可能包含所需的所有信息,但类型推断不会使用它。

您需要制作方法签名的TResult部分,以便推断出类型。 IDataBaseConfigurable<TContract, TSection>是多余的,不需要它是通用的,只需使用{{1}}作为方法的返回类型。

答案 1 :(得分: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()

这消除了对TContractTSection的需求,因为它们在ISourceObserverConfiguration界面中已知。编译器知道接口约束是IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>,它只会起作用。

此外,由于这是一种扩展方法,我们在ISourceObserverConfiguration上定义了一般约束,因此我们需要扩展SourceObserverSection

<小时/> 然后你可以像你想要的那样消费它:

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

<强>更新

根据 OP 对问题的修改/澄清,我有以下内容:

  

是否可以在 C#中使用,或者我应该手动指定它?

您应该手动指定它。根据重新实现的要求推断出这一点, 换句话说,由于您有IDatabaseConfigurable的多个实现,调用者必须通过其TContractTSection约束来指定要使用的实现。

答案 2 :(得分:1)

这取决于您的代码有多灵活,以及您使用它做了什么。通常,不 - 您需要指定 all 泛型类型,或者不指定它们。

这意味着简单地传递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

相关问题