C# "Semi-Generic"接口?



概述

简而言之,我希望创建一个由两个类实现的接口。但是,由于其用例,其中的接口和方法很难声明。

实质上,这是实现此新接口的两个类。在每种情况下Foo()接口方法以某种方式修改引用的对象,并输出原始对象的副本。

public class MyClass1
{
private int _value;
public MyClass1(int val) { _value = val; }
public void Foo(out MyClass1 result)
{
result = new MyClass1(_value);
_value += 1;
}
}
public class MyClass2
{
private double _value;
public MyClass2(double val) { _value = val; }
public void Foo(out MyClass2 result)
{
result = new MyClass2(_value);
_value += 1.5;
}
}

我希望找到最直观、最优雅的方式来声明和实现interface IInterface以及void Foo()

尝试的解决方案

以下是我尝试过的一些选项以及每个选项的明显优缺点。

选项 1:创建通用接口

public interface IInterface<T> where T : class
{
void Foo(out T result);
}

优点

  • 允许在每个类中进行泛型实现
  • 其他地方易于使用的方法
  • 类型安全的方法实现和使用

缺点

  • 令人困惑的类定义

这个解决方案唯一明显的问题是,在我们的例子中,T的"泛型"类型总是实现接口的类,而不是任意类型(即IEnumerable<T>)。虽然此解决方案运行良好,但它确实会导致类实现中出现丑陋的冗余:

public class MyClass1 : IInterface<MyClass1> // Ew!
{
// ...
public void Foo(out MyClass1 result) { /* ... */ }
}

尽管类定义很奇怪,但该方法的用法非常简单:

void Bar(MyClass1 orig)
{
orig.Foo(out MyClass1 copy);

// ...
}

选项 2:使接口方法本身泛型

public interface IInterface
{
void Foo<T>(out T result) where T : IInterface; // Gross!
}

优点

  • 简单的类定义

缺点

  • 令人困惑的方法声明
  • 需要在方法实现中添加类型安全逻辑
  • 在其他地方容易滥用方法

此选项有更大的问题。方法声明比较混乱,实现中需要额外的错误处理:

public class MyClass1 : IInterface
{
// ...
public void Foo<T>(out T result) where T : IInterface // Gross again!
{
// Nothing preventing mismatched types here...
if (typeof(T) != typeof(MyClass1))
throw new Exception("Uh oh!");
// ...
}
}

如前所述,由于更宽松的类型限制,也很容易滥用该方法:

void Bar(MyClass1 orig)
{
// Type mismatch!
orig.Foo<MyClass2>(out MyClass2 copy);

// ...
}

选项 3:使用接口作为输出参数

public interface IInterface
{
void Foo(out IInterface result);
}

优点

  • 简单的类定义
  • 简单方法声明

缺点

  • 方法实施和使用中的类型不匹配危险

虽然此解决方案消除了所有冗余泛型,但遗憾的是,它在方法的实现和使用中为类型不匹配错误留下了空间。

public class MyClass1 : IInterface
{
// ...
public void Foo(out IInterface result)
{
// ...
// Could output MyClass2 instead!
result = new MyClass1(_value);
}
}

上述问题相当微不足道,但以下情况绝对是潜在的问题:

void Bar(MyClass1 orig)
{
orig.Foo(out IInterface copy);
// Can cast copy as wrong type
MyClass2 a = (MyClass2)copy;
// Same problem can happen with 'as' keyword
MyClass2 b = copy as MyClass2;

// ...
}

最终目标

虽然我认为目前不可能,但能够使用仅限于实现类的"伪泛型"out T定义接口方法Foo()会更干净。像这样的东西将是理想的:

public interface IInterface
{
// In this context, I would want "this" or some other keyword
// (self, etc.) to indiciate the class implementing the interface
void Foo(out T result) where T : this;
}
public class MyClass1 : IInterface
{
// In an ideal world, this would be valid and correct
void Foo(out MyClass1 result) { /* ... */ }
}

此解决方案将:

  • 消除丑陋、冗余的类定义
  • 保持方法实现简单且类型安全
  • 允许在其他情况下使用简单、安全的方法(见下文)
void Bar(MyClass1 orig)
{
// Allow concrete type here rather than interface
orig.Foo(out MyClass1 copy);
// ...
}

总结

感谢您的阅读!很想听听任何人对用于此类情况的最佳方法或我尝试过的解决方案的任何潜在替代方案的想法。

没有通用的where T : this约束。如果您认为这是一个需要修复的问题,您可以向 https://github.com/dotnet/csharplang 提交请求

最接近的替代方案可能是"奇怪的重复模板模式":interface IInterface<T> where T : IInterface<T>,但在这种情况下,与第一个替代方案相比,它似乎没有添加任何内容。

我不同意第一种选择有一个令人困惑的类声明。虽然重复了该类型,但这是一个相当小的问题,似乎只发生在相当特定的情况下。

与其走接口路线,不如改用子类化吗? 使每个类的顶层派生自父类。 更简单的声明。 然后,您的其余类可以包含有关它们自己的其他不同内容。 类似的东西

public class BaseClass<T,T2> where T2 : new()
{
public virtual T2 Foo()
{
var newObj = new T2();
// anything else you want to do with it?
return newObj;
}
protected T _value;
}
public class BaseOne : BaseClass<int, BaseOne>
{
public BaseOne()
{
_value = 1;
}
}
public class BaseTwo : BaseClass<double, BaseTwo>
{
public BaseTwo()
{
_value = 1.5;
}
}

在声明中,第一个"T"是 int 或 double 的数据类型。 第二个"T2"要求允许通过"new()"调用创建。 因此,通过将要构建的类作为 T2 提供,您可以直接创建该类类型。

由于每个单独的子类都有其没有参数的默认构造函数,并且"_value"属性受保护(可在父类的子类级别更改),因此它们可以根据需要分别将值启动为 1 或 1.5(或您实际需要的任何值)。

因此,每种类型的构造函数分别使用 1 或 1.5 处理启动"_value"。 单个 FOO 调用创建并返回新实例,而不会出现问题。 然后,当尝试分别创建每个对象时,它看起来很简单,如下所示

var x1 = new BaseOne();
var y1 = new BaseTwo();

因为每个类声明已经声明为:

BaseClass<T,??> of <int, BaseOne> or <double, BaseTwo> 

分别,在 new() 调用中完全屏蔽该类型。

然后,获取相同类型的新实例

var x2 = x1.Foo();
var y2 = y1.Foo();

此外,如果您尝试继承一堆其他属性/值,您可能正在寻找 CLONE(),而不是执行 NEW() 调用。

最新更新