在 C# 中使用泛型的相互依赖的接口

Posted

技术标签:

【中文标题】在 C# 中使用泛型的相互依赖的接口【英文标题】:Interdependent interfaces using generics in C# 【发布时间】:2011-12-19 21:07:08 【问题描述】:

如果让您感到困惑,我深表歉意。

我有这两个主要界面。首先是ISensor接口:

public interface ISensor<TReading>
    where TReading : ISensorReading<ISensor<TReading>>

    event SensorReadingCompletedEH<ISensor<TReading>, TReading> ReadCompleted;
    TReading Read();

还有第二个接口:ISensorReading:

public interface ISensorReading<TSensor>
    where TSensor : ISensor<ISensorReading<TSensor>>

    TSensor Sensor  get; 

会导致如下错误:

ISensor 类型必须可转换为 ISensor>> 以便将其用作泛型类型或方法中的参数 TSensor ISensorReading

类型 ISensorReading 必须可转换为 ISensorReading>> 才能将其用作泛型类型或方法 ISensor 中的参数 TReading

我担心这是由于编译时无法解决的循环引用;但是我想确保派生类型如 TelemetricSensor 的一致性:ISensor&lt;TelemetricReading&gt; 和 遥测阅读:ISensorReading&lt;TelemetricSensor&gt;

我应该使用哪些其他方法来实现简单的强制转换和类型安全?

我正在使用 .NET 2.0 和 VS2005

【问题讨论】:

【参考方案1】:

你是对的——你不能以这种方式使用泛型类型约束来定义它们,因为它会导致无限嵌套。

你可以做的是有一个具体的实现,例如:

public interface ITelemetricSensorReading : ISensorReading

然后约束传感器强制执行:

public interface ITelemetricSensor<TReading> : ISensor<TReading> where TReading : ITelemetricSensorReading

【讨论】:

【参考方案2】:

说,你定义了你的两种类型:

    class AReading : ISensorReading<ASensor>  
    class ASensor : ISensor<AReading>  

现在,声明 ISensorReading&lt;ASensor&gt; 是非法的,因为 ASensor 没有实现 ISensor&lt;ISensorReading&lt;ASensor&gt;&gt;。相反,它实现了ISensor&lt;Areading&gt;,这是不同的。

请参阅,在 .NET 中,声明 A : B 通常并不暗示声明 I&lt;A&gt; : I&lt;B&gt;。如果你仔细想想,这不一定是真的——取决于I的性质。

您要查找的特征称为“协方差”和“逆变”。这是 C# 的另一个特性,您可以在其中告诉编译器,对于您的特定接口 I,上述含义实际上确实成立(协方差),或者相反的含义 I&lt;B&gt; : I&lt;A&gt; 成立(逆变)。

您使用out 关键字实现的第一个:

    interface I<out T>  ... 

第二个 - 使用 in 关键字:

    interface I<in T>  ... 

然而,不幸的是,泛型类型参数中的协变和逆变只在 C# 4.0 中引入,所以你在这里不走运。

您可以升级到 C# 4.0(这是我强烈推荐的),或者您可以依靠您的单元测试来确保所有类型保持一致。

【讨论】:

确实有时协变和逆变会导致头痛。不幸的是,我的应用程序必须尽可能与 MONO 兼容,并且不能选择升级到 C# 4.0。我还注意到 .NET 4.0 不如 .NET 2.0 高效 .net 4.0 效率低于 2.0?究竟如何?【参考方案3】:

感谢大家的回答。

最后,这是我为该问题选择的解决方案。希望有用。如果您有 cmets 或知道在不更改框架的情况下改进它的方法,请告诉我。

首先,我创建了一个要求最低的 ISensor 接口。之后我定义了一个新的 ISensor 通用接口如下:

public delegate void SensorErrorEventHandler<TSensor>(TSensor sensor, ISensorError error)
    where TSensor : ISensor;

public delegate void SensorReadingCompletedEventHandler<TSensor, TReading>(TSensor sensor, TReading[] read)
    where TSensor : ISensor
    where TReading : ISensorReading<TSensor>;

public interface ISensor : IDisposable

    bool IsOpen  get; 
    bool Started  get; 
    void Connect();
    void Disconnect();
    void Start();
    void Stop();


public interface ISensor<TSensor, TReading> : ISensor
    where TSensor : ISensor
    where TReading : ISensorReading<TSensor>

    TReading[] LastReadings  get; 
    event SensorErrorEventHandler<TSensor> Error;
    event SensorReadingCompletedEventHandler<TSensor, TReading> ReadCompleted;
    bool Read(out TReading[] readings);


public interface ISensorReading<TSensor> where TSensor : ISensor

    TSensor Sensor  get; 
    bool Mistaken  get; 

定义了这些接口并遵循相同的结构,我能够进行第一个实现:具有对应 ITelemetricReading 的 TelemetricSensor 类

public delegate void TelemetricSensorThresholdExceededEventHandler<TSensor>(TSensor sensor)
    where TSensor : ITelemetricSensor;

public interface ITelemetricSensor : ISensor

    /* Properties, events and methods */


public interface ITelemetricReading : ISensorReading<ITelemetricSensor>

    /* Properties, events and methods */


public abstract class TelemetricSensor<TSensor, TReading> : ITelemetricSensor, ISensor<TSensor, TReading>
    where TSensor : ITelemetricSensor
    where TReading : ITelemetricReading, ISensorReading<TSensor>

    public abstract TReading[] LastReadings  get; 
    public event SensorErrorEventHandler<TSensor> Error;
    public event SensorReadingCompletedEventHandler<TSensor, TReading> ReadCompleted;

    public abstract bool Read(out TReading[] readings);

TelemetricSensor 抽象类的 TReading 很有趣。它被定义为

TReading : ITelemetricReading, ISensorReading<TSensor>

这可能看起来是多余的,因为 ITelemetricReading 继承自 ISensorReading,但需要编译代码并满足两者的要求

TReading[] LastReadings  get; 

财产和

bool Read(out TReading[] readings);

方法。最后,为了跳过繁琐的转换(可能还有一些错误),可以对 Read(...) 进行多次重载,以便每个都提供正确转换的数据并满足所有接口实现。

【讨论】:

以上是关于在 C# 中使用泛型的相互依赖的接口的主要内容,如果未能解决你的问题,请参考以下文章

C# 泛型的使用

这是引用包含泛型的 C# 接口的 VB.NET 实现的 FxCop 错误吗?

关于 c# 接口和泛型的问题

C#泛型实例详解

C#进阶C# 泛型

带有泛型的 Dagger Hilt 缺失/绑定接口