通用方法多重(OR)类型约束


135

读完这篇文章后,我了解到可以通过使方法成为通用方法来允许方法接受多种类型的参数。在示例中,以下代码与类型约束一起使用,以确保“ 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
您可以在这种方法的主体内以一般方式有用地做些什么(除非多个类型都从特定的基类派生,在这种情况下为什么不将其声明为类型约束)?
Damien_The_Unbeliever'5

@Damien_The_Unbeliever不知道您体内的意思是什么?除非您的意思是允许任何类型并手动检查正文...以及我要编写的实际代码(最后一个代码片段),否则我希望能够传递字符串或异常,因此没有类那里的关系(我想像的system.object除外)。
曼斯菲尔德

1
另外请注意,有书面形式没有用where T : string,因为string是一个密封类。您唯一可以做的就是为string和定义重载T : Exception,如@ Botz3000在下面的答案中所述。
马蒂亚斯·布伦斯

但是,当没有关系时,您可以调用的唯一方法arg是由object- 定义的方法,那么为什么不从图片中删除泛型并设置类型arg object呢?你得到什么?
Damien_The_Unbeliever,2012年

1
@Mansfield您可以创建一个接受对象参数的私有方法。两个重载都将调用那个。这里不需要泛型。
Botz3000

Answers:


68

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

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

如果它们是泛型类的一部分,则它们将比该方法的泛型版本更可取。


1
有趣。这发生在我身上,但我认为使用一个函数可以使代码更整洁。嗯,非常感谢!出于好奇,您是否知道是否有特定原因无法实现此目的?(是否故意将其排除在语言之外?)
曼斯菲尔德(Mansfield

3
@Mansfield我不知道确切的原因,但我认为您将无法再以有意义的方式使用通用参数。在类内部,如果允许它们具有完全不同的类型,则必须将它们视为对象。这意味着您也可以省去泛型参数并提供重载。
Botz3000

3
@Mansfield,这是因为or关系使事情变得远非一般有用。您必须进行反思以弄清楚该做什么以及所有这些。(Y!)
克里斯·普弗

29

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

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

如果您给我一个知道如何执行类型T所知道的事情的对象,那么我可以传递“ a”:我声明的类型的返回值,或者“ b”:某种使用这种类型。

如果您一次尝试给它一个以上的类型(通过使用or),或者试图使其返回一个可能不止一种类型的值,则契约变得模糊:

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

问题是,当您进入方法时,您不知道他们是否给了您an IJumpRope或a PiFactory。此外,当您继续使用该方法(假设已经对其进行了神奇的编译)时,您实际上不确定是否具有a Fisher或an AbstractConcreteMixer。基本上,这会使整个过程更加混乱。

解决问题的方法是两种可能性之一:

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

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

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

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

public interface IHasMessage
{
    string GetMessage();
}

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

现在,您所需要的只是将a string和an Exception转换为IHasMessage的方法。好简单。


@ Botz3000,对不起,只是发现我拼错了你的名字。
克里斯·普弗

否则,编译器可能将该类型威胁为函数内的联合类型,并使返回值与它所进入的类型相同。TypeScript会执行此操作。
亚历克斯

@Alex,但这不是C#所做的。
克里斯·普弗

没错,但是可以。我读了这个答案,因为那是做不到的,我误解了吗?
Alex

1
事实是,与例如C ++模板相比,C#泛型参数约束和泛型本身非常原始。C#要求您事先告诉编译器对泛型类型允许哪些操作。提供该信息的方法是添加一个实现接口约束(其中T:IDisposable)。但是,您可能不希望您的类型实现某种接口以使用通用方法,或者您可能希望允许通用代码中的某些类型没有通用接口。例如 允许任何结构或字符串,以便您可以简单地调用Equals(v1,v2)进行基于值的比较。
Vakhtang

8

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

好主意,除了我不认为我能够做到这一点,因为我想使用根据上面的注释是密封的类的字符串……
曼斯菲尔德

这实际上是一个设计问题。泛型函数用于执行代码复用的类似操作。如果您计划在方法主体中执行不同的操作,则最好使用分离方法(IMHO)。
daryal

实际上,我正在做的是编写一个简单的错误记录功能。我希望最后一个参数可以是有关错误的信息字符串,也可以是异常,在这种情况下,无论如何我都将e.message + e.stacktrace保存为字符串。
曼斯菲尔德

您可以编写一个具有isSuccesful的新类,将消息和异常另存为属性。然后您可以检查是否成功,然后执行其余操作。
daryal

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.Primary;
        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期望传递值的任何位置。如果确实传递了期望EitherTATP,但Either包含错误的值类型,则会出现运行时错误。

我通常在需要方法返回结果或错误的地方使用此方法。它确实清除了该样式代码。我也偶尔(很少)用它代替方法重载。实际上,这是对这种过载的非常差的替代。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.