泛型方法多重 (OR) 类型约束

本文关键字:类型 约束 OR 泛型方法 | 更新日期: 2023-09-27 18:35:48

阅读本文,我了解到可以通过使方法成为泛型方法来允许该方法接受多种类型的参数。在此示例中,以下代码与类型约束一起使用,以确保"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 可以是一种ParentClassChildClass,如下所示:

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

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

泛型方法多重 (OR) 类型约束

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

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

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

> Botz 的答案是 100% 正确的,这里有一个简短的解释:

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

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

如果您尝试一次为其提供多个类型(通过具有 or )或尝试让它返回可能为多个类型的值,则合约会变得模糊:

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

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

问题的解决方案有两种可能性之一:

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

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

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

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

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

现在,您所需要的只是将stringException转换为 IHasMessage 的方法。非常容易。

如果 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
}

尽管这个问题很古老,但我仍然对上面的解释获得随机赞成票。解释仍然完全正确,但是我将再次回答一个类型,该类型非常适合我作为联合类型的替代品(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
  • 您可以通过PrimaryAlternate访问该值
  • TP/TA 和 Both<TP、TA> 之间有隐式转换器,允许您将值或Either传递到任何预期的位置。如果确实传递了预期TATPEither,但Either包含错误类型的值,则会收到运行时错误。

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