强制执行通用接口子类型

4

我有一个通用接口 (MyInterface<T>),在下面的示例中由类 ChildA 实现:

public interface MyInterface<T>
{
    MyObj<T> GetObj(); // Irrelevant
}

class ChildA : MyInterface<ChildA>
{
    // Irrelevant:
    MyObj<ChildA> GetObj() {
        return new MyObj<ChildA>();
    }
}

这个方法可以实现,但我需要确保 <T> 始终是 实现 类的类型,所以在这种情况下 T 应该始终是 ChildA 的类型,因为它是由ChildA 实现的。
另一个正确的实现可以是这样的,例如:
class ChildB : MyInterface<ChildB> { ... }

但是当前,这种不正确的实现也是可能的,而事实上它不应该存在:

class ChildA : MyInterface<ChildB> { ... }

有没有办法强制执行这个规定呢?

2
我不确定你在这里尝试做什么。虽然可以强制执行其自身的类型,但几乎没有什么好处。背后的动机是什么? - Mio Bambino
1
你为什么想要这个? - Nick Bull
@DmytroShevchenko 我知道可能有更好的方法实现这个,但是我目前没有时间去改变它。如果有人能告诉我是否可以实现所给定的情况,那么我将非常感激。 - Duncan Lukkenaer
1
这种方法怎么样? public interface MyInterface { MyObj<MyInterface> GetObj(); } - Dmytro Shevchenko
3
顺便提一下,这被称为“奇妙递归模板模式”,并且不,没有办法在C#中添加此限制。引用@EricLippert的话,“[该模板]似乎是机制的滥用,而不是从程序的‘业务领域’对概念进行建模”。 - vgru
显示剩余4条评论
4个回答

7

无法强制实现类型的通用类型参数被限制。

可用的类型约束如下:

  • where T : struct
  • where T : class
  • where T : new()
  • where T : <base class name>
  • where T : <interface name>
  • where T : U

C# 中没有像 where T : self 这样的东西。实际上,这甚至没有意义,因为这样的事情不能有意义地被执行。此外,它也不适合于协变性/逆变性概念,并且从一般角度来看会很奇怪继承。

你能做到的最接近的事情是这样的:

public interface IMyInterface<T> where T : IMyInterface<T>
{
    MyObj<T> GetObj();
}

为什么这没有意义

假设你可以做到这一点:

public interface IMyInterface<T> where T : self // this syntax does not exist in C#
{
    MyObj<T> GetObj();
}

现在所有实现类型都必须使用自己作为类型参数。但是你仍然可以这样做:
public class ChildC<T> : IMyInterface<T> where T : self
{
    /* ... */
}

这将绕过您的限制。



最后一个声明是否也需要 where T : self 才能编译? - vgru
@Groo 可能吧 :) 这取决于语言作者希望它如何工作。 - Dmytro Shevchenko
现在你需要将限制添加到通用类中,因此使用同样的假设“self”约束进行应用,它很可能会与该类正常工作。如果我没错的话,这也可能需要使类sealed,因为你将无法合法地从它派生。 - vgru
@Groo 好的,已添加约束条件。 - Dmytro Shevchenko
如果假设的“self”指的是约束声明符或其子级,那对我来说仍然有意义。因为仅仅指定声明符是毫无意义的。我说得对吗? - romain-aga
@romain-aga 嗯,说实话,对我来说两种方式都没有太多意义 :( - Dmytro Shevchenko

2

有没有办法强制执行这个规定?

嗯,通用的约束条件无法实现这个功能。但是你可以通过反射来实现,不过我不建议这样做:

public abstract class BaseChild<T> : MyInterface<T>
{
    protected BaseChild()
    {
        if (typeof(T) != this.GetType())
        {
            throw new InvalidOperationException(string.Format(
                          "Type {0} is not supported as valid type parameter for type {1}",
                             typeof(T).Name, this.GetType().Name));
        }
    }
}

例子:

class ChildA : BaseChild<int> { }

// Bang! throws
var instance = new ChildA(); 

.

class ChildB : BaseChild<ChildB> { }

// Ok here
var instance = new ChildB(); 

1
你无法这样做,但是你可以创建自己的控件来比较接口的通用类型和你的类的类型。请参考以下示例:
class ChildA : MyInterface<ChildB>
{
    public ChildA()
    {
        this.ValidateGenericType();
    }

    public MyObj<ChildB> GetObj()
    {
        return new MyObj<ChildB>();
    }

    protected void ValidateGenericType()
    {
        //throws an Exception because ChildB is different of ChilA
        if (this.GetType().Name != this.GetType().GetInterfaces()[0].GetGenericArguments()[0].Name)
        {
            throw new Exception("The generic type must be of type ChildA.");
        }
    }
}

1
似乎你应该使用扩展方法来代替强制实现某个接口来达到这个目的。
public interface ISomeInterface {}

public class Child: ISomeInterface {}

public class OtherChild : ISomeInterface { }

public static class MyInterfaceExtensions
{
    public static MyObj<T> GetMyObj<T>(this T child) where T : ISomeInterface
    {
        return new MyObj<T>();
    }
}

public static class Test
{
    public static void RunTest()
    {
        var child = new Child();
        var otherChild = new OtherChild();

        MyObj<Child> myObj = child.GetMyObj();
        MyObj<OtherChild> myOtherObj = otherChild.GetMyObj();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接