C#泛型类型与多个类约束

14

TL;DR

我希望这段代码能够在C#中编译通过:

public void some_method< T >() where T : class1, class2

是否可能?


完整内容

我有两个方法,除了一个参数以外完全相同。

public SignInResponseMessage Generate(SignInRequestMessage request, (X509Certificate2 || WindowsPrincipal) principal, Uri requestUri)
{
    SignInResponseMessage response = null;
    ClaimsIdentity identity = null;

    if (principal != null)
    {
        identity = CreateSubject(principal);
        response = Generate(request, requestUri, identity);
    }
    else
    {
        throw new ArgumentNullException("principal");
    }

    return response;
}

我目前正在复制这种方法,但它让我有点难以接受,因为我真的想让它更加DRY。看了一下这份文档,似乎很有前途,但它只允许我添加单个类约束。在第二个类上我会得到以下错误:

Error 1 The class type constraint 'class2' must come before any other constraints

如果WindowsPrincipalX509Certificate2是我编写的两个类,我可以轻松地让它们实现相同的接口,然后就完成了,但这不是一个选项。

是否有任何方法可以完成我想做的事情?

如果没有,我想更多地了解使这种操作不可能的基本机制。


4
不可能。一个潜在的解决方案是使用适配器模式,其中适配器实现你的接口,然后使用你的接口作为限制条件。 - David L
你有适配器模式的好资源吗?这对我来说是新的。 - Matt
1
四人帮已经很好地描述了它。您可以使用以下资源:http://www.blackwasp.co.uk/Adapter.aspx - David L
C# 不允许多重继承,因此您不能将两个类用作类型约束。 - Michael Blackburn
你可以使用 where T : class1, interface2where T : class, interface1, interface2。然后通过巧妙地设置接口,你可以接近你想要的东西。 - S. Rojak
@Matt,这是策略模式,不是适配器模式。适配器是使一种类型适合现有框架的能力。策略是在运行时替换实现的能力。http://www.dofactory.com/net/strategy-design-pattern - Michael Blackburn
2个回答

8

我担心这可能导致出现调用哪个方法的问题。想象一下,如果其中一个类继承自另一个类并且重写了该方法,会怎样!?

请参见“菱形继承问题”以完整描述原因。

如果你想解决这个问题,你可以使用适配器来设置通用共享接口,然后使用它。

interface IAdaper {
    SomeMethod();
}

class AdapterOne : IAdapter {
    TypeOneToCall  _one;

    public AdapterOne (TypeOneToCall one) {
        _one = one;
    }

    public SomeMethod() {
        return _one.SomeMethod();
    }
}

class AdapterTwo : IAdapter {
    TypeTwoToCall _two;

    public AdapterTwo (TypeTwoToCall two) {
        _two = two;
    }

    public SomeMethod() {
        return _two.SomeMethod();
    }
}

class Generic<T> where T : IAdapter {

    // Your implementation here.
}

2
创建一个抽象类(IAdapter)的建议是个好主意。在这种情况下,您甚至不需要 Generic 类成为泛型,因为它不需要知道 IAdapter 的背后是什么。 - Yacoub Massad
1
此外,C# 的设计者们非常注重一致性,我个人倾向于同意他们的观点。如果它在所有情况下都不能很好地工作,那么它往往会被排除在外。 - tigerswithguitars
@MichaelBlackburn 确实,我错过了他的评论。 - David L
1
@Matt,方法调用看起来像是public SignInResponseMessage Generate(SignInRequestMessage request, IAdapter principal, Uri requestUri)而不是一个通用的方法。 - Michael Blackburn
1
我已经在各种应用中使用了这个模式。同时也有类似处理 DI 中静态的东西。这与策略模式非常相关。IAdapter 是一种策略。 - tigerswithguitars
显示剩余7条评论

6
如果你将方法作为参数传递,那么 T 可以是任何类型:
  public SignInResponseMessage Generate<T>(SignInRequestMessage request, 
                                          Func<T, ClaimsIdentity> createSubject, 
                                          T principal, 
                                          Uri requestUri)
  {
      SignInResponseMessage response = null;
      ClaimsIdentity identity = null;

      if (principal != null)
      {
          identity = createSubject(principal);
          response = Generate(request, requestUri, identity);
      }
      else
      {
          throw new ArgumentNullException("principal");
      }

      return response;
  }

因此,要重复使用该方法:

  var r1 = Generate<X509Certificate2>(request, CreateSubject, certificate, uri);
  var r2 = Generate<WindowsPrincipal>(request, CreateSubject, principal, uri);

1
这肯定更加流畅并且可行,但是我倾向于避免使用委托,因为它们会使代码更难以阅读。 - Matt
1
这是一种非常实用的方法。如果做得好,它可以像任何面向对象模式一样干净易读。而且代码更少。不要轻易放弃它。 - tigerswithguitars
1
我不同意我的过去自己,这是一个好的方法。 - Matt

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