【问题标题】:Generic method multiple (OR) type constraint泛型方法多 (OR) 类型约束
【发布时间】:2012-06-05 17:29:50
【问题描述】:

阅读this,我了解到可以通过将其设为泛型方法来允许方法接受多种类型的参数。在示例中,以下代码与类型约束一起使用,以确保“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
}

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

【问题讨论】:

  • 在这种方法的主体中以通用方式你能做什么有用(除非多种类型都派生自一个特定的基类,在这种情况下为什么不将其声明为类型约束)?
  • @Damien_The_Unbeliever 不确定你所说的身体是什么意思?除非您的意思是允许任何类型并手动检查正文...以及我要编写的实际代码(最后一个代码 sn-p),否则我希望能够传递字符串 OR 异常,所以有那里没有类关系(我想象的 system.object 除外)。
  • 还要注意写where T : string是没有用的,因为string是一个密封类。您可以做的唯一有用的事情是为stringT : Exception 定义重载,正如@Botz3000 在下面的回答中所解释的那样。
  • 但是当没有关系时,你可以在arg上调用的唯一方法是object定义的方法——所以为什么不从图片中删除泛型并制作arg的类型@ 987654338@?你得到了什么?
  • @Mansfield 您可以创建一个接受对象参数的 private 方法。两个重载都会调用那个。这里不需要泛型。

标签: c# asp.net-mvc-3 types


【解决方案1】:

尽管这个问题很老,但我仍然对我上面的解释随机投赞成票。解释仍然很好,但是我将第二次回答一个对我有用的类型作为联合类型的替代品(对 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 和 Either 之间存在隐式转换器,允许您将值或 Either 传递到预期的任何地方。如果您确实传递了 Either ,而 TATP 是预期的,但 Either 包含错误类型的值,您将收到运行时错误。

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

【讨论】:

  • 基于这个答案,我创建了一个AnyOf 类型,它的作用与Either 类型相同,但支持更多类型,最多5 个。有关详细信息,请参阅github.com/StefH/AnyOf。跨度>
【解决方案2】:

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

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

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

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

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

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

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

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

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

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

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

public interface IHasMessage
{
    string GetMessage();
}

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

现在您需要的只是将stringException 转换为IHasMessage 的方法。很简单。

【讨论】:

  • 抱歉@Botz3000,刚刚注意到我拼错了你的名字。
  • 或者编译器可能会将该类型威胁为函数中的联合类型,并使其返回值与它进入的类型相同。TypeScript 会这样做。
  • @Alex 但这不是 C# 所做的。
  • 这是真的,但它可以。我读了这个答案,因为它不能,我误解了吗?
  • 问题是 C# 泛型参数约束和泛型本身与 C++ 模板相比非常原始。 C# 要求您提前告诉编译器在泛型类型上允许哪些操作。提供该信息的方法是添加一个实现接口约束(其中 T :IDisposable)。但是您可能不希望您的类型实现某些接口以使用泛型方法,或者您可能希望在泛型代码中允许某些类型没有通用接口。前任。允许任何结构或字符串,以便您可以简单地调用 Equals(v1, v2) 进行基于值的比较。
【解决方案3】:

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

【讨论】:

  • 好主意,但我不认为我能做到这一点,因为我想使用上面评论中的密封类字符串...
  • 这其实是一个设计问题。通用函数用于通过代码重用执行类似的操作。如果您打算在方法主体中执行不同的操作,那么分离方法是一种更好的方法(恕我直言)。
  • 我在做的,其实就是写一个简单的错误记录函数。我希望最后一个参数是有关错误的信息字符串或异常,在这种情况下我将 e.message + e.stacktrace 保存为字符串。
  • 你可以写一个新类,拥有isSuccesful,将消息和异常保存为属性。然后你可以检查issuccesful是否为真,然后做剩下的。
【解决方案4】:

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

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

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

【讨论】:

  • 有趣。我想到了这一点,但我认为使用一个函数可能会使代码更简洁。嗯嗯,非常感谢!只是出于好奇,您知道是否有特定原因这是不可能的吗? (是否故意将其排除在语言之外?)
  • @Mansfield 我不知道确切的原因,但我认为您将无法再以有意义的方式使用泛型参数。在类内部,如果允许它们具有完全不同的类型,则必须将它们视为对象。这意味着您也可以省略泛型参数并提供重载。
  • @Mansfield,这是因为or 关系使事情变得非常有用。您将必须进行反思以弄清楚该做什么以及所有这些。 (糟糕!)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多