【问题标题】:Simplify generic type inferring简化泛型类型推断
【发布时间】:2016-05-06 12:31:49
【问题描述】:

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

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

您可以看到我必须指定对 &lt;SourceObserverContract, SourceObserverSection&gt; 两次,这违反了 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);
}

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

【问题讨论】:

  • 如果您的方法签名是IDatabaseConfigurable&lt;TContract, TSection&gt; LoadFromAnySource(this TSection section, string serviceBaseUri, string nodeName, Func&lt;TContract&gt; contractCreator)(或只是TContract contract),则可以从用法中推断出类型。
  • 这怎么违反了 DRY 原则?这比“不要重复写同一件事”要复杂一些。你可能不在乎你在twitter 中写了三遍t,是吗? :D 两者相同 - 一个比另一个更通用。无论如何,@JeroenMostert 的评论几乎是最好的方法,应该完全是一个答案。虽然可能像程序员这样的地方而不是 SO - 我认为这对 SO 来说不是一个好问题,真的。
  • @JeroenMostert 是的,但他们不知道要使用哪个 IDatabaseConfigurable
  • 是的,对不起,显然我的意思是Func&lt;IDatabaseConfigurable&lt;...&gt;&gt;。 “有效”结果是实现接口的结果——如果需要更多的东西,那么设计就有问题。 (事实上​​,我很确定设计存在问题,需要进行一些重构,但这并不完全是主题。)
  • “错误”太强了。我只是说您可能可以以一种类型推断将隐式工作的方式构造您的代码(或者您应该使用较少的泛型)。这是否真的可行,以及您是否想要结果是完全不同的事情。如果不进一步深入研究您的代码,我无法有意义地回答这些问题,但这会退化为设计讨论,这是主观的并且明确不是 Stack Overflow 是关于什么的。你从字面上问的问题(“我能以某种方式避免用我所拥有的东西做这件事吗”)的答案是不,简单明了。

标签: c# .net generics


【解决方案1】:

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

您需要将TContract 作为方法签名的一部分,以便可以推断出类型。 TResult 是多余的,不需要泛型,直接使用IDataBaseConfigurable&lt;TContract, TSection&gt; 作为方法的返回类型即可。

【讨论】:

【解决方案2】:

这在某种程度上取决于您的代码有多灵活,以及您如何使用它。一般来说,不需要 - 您要么需要指定 所有 泛型类型,要么不需要指定它们。

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

【讨论】:

  • 我知道,但有时可以通过在类级别提取某些类型来避免这种情况。例如Foo.Bar&lt;A,B,C&gt;(B b, C c) 当我们可以推断出BC 让我们指定所有参数,但Foo&lt;A&gt;.Bar(B b, C c) 可以避免它。但我不明白我怎样才能在这里制作相同(或相似)的技巧......
  • 在这种情况下,不,因为所有类型都来自通用参数(ConfigurationSection 除外,我确实移动了它 - 与您在此处的示例相同)。由于我们不传递类型,因此没有协变接口或非泛型接口是不可能的
  • 我赞成它,但不幸的是我不能使用它,因为被调用者应该得到一个精确类型的最终结果。当然,它可以转换它,但协方差为我们做所有事情会更好。
【解决方案3】:

使用LoadFromAnySource 方法的当前方法签名,这不能如您所愿推断。但是,这可以通过修改LoadFromAnySource 签名来推断。

既然您已经知道ISourceObserverConfiguration 接口(并且由此我们知道它重新实现了IDatabaseConfigurable&lt;SourceObserverContract, SourceObserverSection&gt; 接口),请在您的方法声明中使用它作为通用约束:

代替

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&lt;SourceObserverContract, SourceObserverSection&gt;,它会正常工作。

此外,由于这是一个扩展方法,并且我们在ISourceObserverConfiguration 上定义了一个通用约束,我们需要扩展SourceObserverSection


然后你可以完全按照你的意愿消费它:
sourceObserverSection.LoadFromAnySource<SourceObserverConfiguration>
   (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

更新

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

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

您应该手动指定它。 不可能根据重新实现的要求来推断这一点,其中基本接口定义了您的顶级约束试图解决的具体类型。换句话说,由于您有多个IDatabaseConfigurable 实现,调用者必须通过其TContractTSection 约束指定要使用的实现。

【讨论】:

  • 这将无法编译,因为您不再定义 TSection
  • 我更新了我的答案并希望得到支持,因为它现在是正确的
  • ISourceObserverConfiguration是具体接口之一,我有多个接口,继承IDatabaseConfigurable。所以很遗憾我不能在这里写ISourceObserverConfiguration,它应该更通用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-17
相关资源
最近更新 更多