163

阅读本文,我了解到可以通过将其设为泛型方法来允许方法接受多种类型的参数。在示例中,以下代码与类型约束一起使用,以确保 "U" 是IEnumerable<T>.

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

我发现了一些允许添加多个类型约束的代码,例如:

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

但是,此代码似乎强制要求arg必须同时是ParentClass ChildClass的类型。我想做的是说 arg 可以是一种类型ParentClass ChildClass以下列方式:

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

一如既往地感谢您的帮助!

4

4 回答 4

76

这是不可能的。但是,您可以为特定类型定义重载:

public void test(string a, string arg);
public void test(string a, Exception arg);

如果它们是泛型类的一部分,则它们将优于该方法的泛型版本。

于 2012-05-31T12:50:56.023 回答
32

博茨的回答是 100% 正确的,这里有一个简短的解释:

当您编写一个方法(通用或非通用)并声明该方法采用的参数类型时,您正在定义一个合同:

如果你给我一个知道如何做类型 T 知道如何做的事情的对象,我可以提供“a”:我声明的类型的返回值,或“b”:使用的某种行为那种。

如果你尝试一次给它多个类型(通过一个或)或尝试让它返回一个可能不止一种类型的值,那么合同会变得模糊:

如果您给我一个知道如何跳绳或知道如何将 pi 计算到第 15 位的对象,我将返回一个可以钓鱼或混合混凝土的对象。

问题是,当您进入该方法时,您不知道他们是否给了您一个IJumpRope或一个PiFactory. 此外,当你继续使用该方法时(假设你已经让它神奇地编译)你不确定你是否有一个Fisher或一个AbstractConcreteMixer. 基本上它使整个事情变得更加混乱。

您的问题的解决方案是以下两种可能性之一:

  1. 定义多个方法来定义每个可能的转换、行为或其他。这就是博茨的回答。在编程世界中,这被称为重载方法。

  2. 定义一个基类或接口,该基类或接口知道如何执行该方法所需的所有操作,并让一个方法采用该类型。这可能涉及将一个string和包装Exception在一个小类中,以定义您计划如何将它们映射到实现,但是一切都非常清晰且易于阅读。四年后,我可以来阅读您的代码并轻松理解发生了什么。

您选择哪个取决于选择 1 和 2 的复杂程度以及它需要的可扩展性。

因此,对于您的具体情况,我将想象您只是从异常中提取消息或其他内容:

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

现在您所需要的只是将 astring和 anException转换为 IHasMessage 的方法。很容易。

于 2012-05-31T13:03:52.317 回答
10

如果 ChildClass 表示它是从 ParentClass 派生的,您可以只写以下内容来接受 ParentClass 和 ChildClass;

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

另一方面,如果你想使用两个不同的类型,它们之间没有继承关系,你应该考虑实现相同接口的类型;

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

然后您可以将通用函数编写为;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}
于 2012-05-31T12:55:16.820 回答
7

与这个问题一样古老,我仍然对我上面的解释随机投票。解释仍然很好,但我将再次回答一个对我有用的类型作为联合类型的替代品(对 C# 不直接支持的问题的强类型答案是)。

using System;
using System.Diagnostics;

namespace Union {
    [DebuggerDisplay("{currType}: {ToString()}")]
    public struct Either<TP, TA> {
        enum CurrType {
            Neither = 0,
            Primary,
            Alternate,
        }
        private readonly CurrType currType;
        private readonly TP primary;
        private readonly TA alternate;

        public bool IsNeither => currType == CurrType.Neither;
        public bool IsPrimary => currType == CurrType.Primary;
        public bool IsAlternate => currType == CurrType.Alternate;

        public static implicit operator Either<TP, TA>(TP val) => new Either<TP, TA>(val);

        public static implicit operator Either<TP, TA>(TA val) => new Either<TP, TA>(val);

        public static implicit operator TP(Either<TP, TA> @this) => @this.Primary;

        public static implicit operator TA(Either<TP, TA> @this) => @this.Alternate;

        public override string ToString() {
            string description = IsNeither ? "" :
                $": {(IsPrimary ? typeof(TP).Name : typeof(TA).Name)}";
            return $"{currType.ToString("")}{description}";
        }

        public Either(TP val) {
            currType = CurrType.Primary;
            primary = val;
            alternate = default(TA);
        }

        public Either(TA val) {
            currType = CurrType.Alternate;
            alternate = val;
            primary = default(TP);
        }

        public TP Primary {
            get {
                Validate(CurrType.Primary);
                return primary;
            }
        }

        public TA Alternate {
            get {
                Validate(CurrType.Alternate);
                return alternate;
            }
        }

        private void Validate(CurrType desiredType) {
            if (desiredType != currType) {
                throw new InvalidOperationException($"Attempting to get {desiredType} when {currType} is set");
            }
        }
    }
}

上面的类代表了一个类型,可以TP可以是TA。您可以这样使用它(类型参考我的原始答案):

// ...
public static Either<FishingBot, ConcreteMixer> DemoFunc(Either<JumpRope, PiCalculator> arg) {
  if (arg.IsPrimary) {
    return new FishingBot(arg.Primary);
  }
  return new ConcreteMixer(arg.Secondary);
}

// elsewhere:

var fishBotOrConcreteMixer = DemoFunc(new JumpRope());
var fishBotOrConcreteMixer = DemoFunc(new PiCalculator());

重要笔记:

  • 如果您不先检查,您将收到运行时错误IsPrimary
  • 您可以检查任何IsNeither IsPrimaryIsAlternate
  • Primary您可以通过和访问该值Alternate
  • 在 TP/TA 和 Either<TP, TA> 之间有隐式转换器,允许您传递值或Either期望的任何地方。如果您确实传递了Eitherwhere a TAor TP,但Either包含错误类型的值,您将收到运行时错误。

我通常在我希望方法返回结果或错误时使用它。它确实清理了该样式代码。我也非常偶尔(很少)使用它作为方法重载的替代品。实际上,对于这种过载来说,这是一个非常糟糕的替代品。

于 2019-11-22T15:16:51.870 回答