187

Reading this, I learned it was possible to allow a method to accept parameters of multiple types by making it a generic method. In the example, the following code is used with a type constraint to ensure "U" is an IEnumerable<T>.

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

I found some more code which allowed adding multiple type constraints, such as:

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

However, this code appears to enforce that arg must be both a type of ParentClass and ChildClass. What I want to do is say that arg could be a type of ParentClass or ChildClass in the following manner:

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

Your help is appreciated as always!

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Mansfield
  • 14,445
  • 18
  • 76
  • 112
  • 4
    What could you usefully do, *in a generic manner* within the body of such a method (unless the multiple types all derive from a specific base class, in which case why not declare that as the type constraint)? – Damien_The_Unbeliever May 31 '12 at 12:48
  • @Damien_The_Unbeliever Not sure what you mean by in the body? Unless you mean allowing any type and manually checking in the body...and in the actual code I want to write (the last code snippet), I'd like to be able to pass a string OR exception, so there is no class relationship there (except system.object I imagine). – Mansfield May 31 '12 at 12:50
  • 2
    Also note that there's no use in writing `where T : string`, as `string` is a **sealed** class. The only useful thing you could do is defining overloads for `string` and `T : Exception`, as explained by @Botz3000 in his answer below. – Mattias Buelens May 31 '12 at 12:52
  • But when there's no relationship, the only methods you can call on `arg` are those defined by `object` - so why not just remove generics from the picture and make the type of `arg` `object`? What are you gaining? – Damien_The_Unbeliever May 31 '12 at 12:57
  • Well, I simply wanted to limit arg to being either Exception or string... – Mansfield May 31 '12 at 12:58
  • @Mansfield Better use overloads than using object as parameter. It's better than throwing exceptions at runtime for what could have been avoided at compile time. – Botz3000 May 31 '12 at 13:14
  • @Botz3000 If I use overloads, wouldn't I have to write my code twice? Or would the overload simply call the generic function? And my exception handling is really quite simple if it's not a type I want, nothing complex... – Mansfield May 31 '12 at 13:16
  • 1
    @Mansfield You could make a *private* method that accepts an object parameter. Both overloads would call that one. No generics needed here. – Botz3000 May 31 '12 at 13:22
  • @Botz3000 Looks like I'm doing that then. Thanks! – Mansfield May 31 '12 at 13:27
  • I think the proposed "Type Classes" feature of C# could help out in the future: https://github.com/dotnet/csharplang/issues/110 – Bruno Zell Dec 17 '19 at 15:54

4 Answers4

85

That is not possible. You can, however, define overloads for specific types:

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

If those are part of a generic class, they will be preferred over the generic version of the method.

Botz3000
  • 39,020
  • 8
  • 103
  • 127
  • 2
    Interesting. This had occurred to me, but I thought it might make the code cleaner to use one function. Ah well, thanks a lot! Just out of curiosity, do you know if there is a specific reason this isn't possible? (has it been left out of the language deliberately?) – Mansfield May 31 '12 at 12:55
  • 4
    @Mansfield I don't know the exact reason, but i think you wouldn't be able to use the generic parameters in a meaningful way anymore. Inside the class, they would have to be treated like object if they were allowed to be of completely different types. That means you could just as well leave out the generic parameter and provide overloads. – Botz3000 May 31 '12 at 13:00
  • 4
    @Mansfield, it's because an `or` relationship makes things far to general to be useful. You'd *have* to do reflection to figure out what to do and all that. (YUCK!). – Chris Pfohl May 31 '12 at 13:05
36

Botz answer is 100% correct, here's a short explanation:

When you are writing a method (generic or not) and declaring the types of the parameters that the method takes you are defining a contract:

If you give me an object that knows how to do the set of things that Type T knows how to do I can deliver either 'a': a return value of the type I declare, or 'b': some sort of behavior that uses that type.

If you try and give it more than one type at a time (by having an or) or try to get it to return a value that might be more than one type that contract gets fuzzy:

If you give me an object that knows how to jump rope or knows how to calculate pi to the 15th digit I'll return either an object that can go fishing or maybe mix concrete.

The problem is that when you get into the method you have no idea if they've given you an IJumpRope or a PiFactory. Furthermore, when you go ahead and use the method (assuming that you've gotten it to magically compile) you're not really sure if you have a Fisher or an AbstractConcreteMixer. Basically it makes the whole thing way more confusing.

The solution to your problem is one of two possiblities:

  1. Define more than one method that defines each possible transformation, behavior, or whatever. That's Botz's answer. In the programming world this is referred to as Overloading the method.

  2. Define a base class or interface that knows how to do all the things that you need for the method and have one method take just that type. This may involve wrapping up a string and Exception in a small class to define how you plan on mapping them to the implementation, but then everything is super clear and easy to read. I could come, four years from now and read your code and easily understand what's going on.

Which you choose depends on how complicated choice 1 and 2 would be and how extensible it needs to be.

So for your specific situation I'm going to imagine you're just pulling out a message or something from the exception:

public interface IHasMessage
{
    string GetMessage();
}

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

Now all you need are methods that transform a string and an Exception to an IHasMessage. Very easy.

Botz3000
  • 39,020
  • 8
  • 103
  • 127
Chris Pfohl
  • 18,220
  • 9
  • 68
  • 111
  • 1
    sorry @Botz3000, just noticed I misspelled your name. – Chris Pfohl Apr 06 '16 at 15:26
  • 1
    Or the compiler could threat the type as an union type within the function and have the return value be of the same type that it gets in. TypeScript does this. – Alex Jan 30 '18 at 18:42
  • @Alex but that's not what C# does. – Chris Pfohl Jan 30 '18 at 19:05
  • That is true, but it could. I read this answer as that it wouldn't be able to, did I misinterpret? – Alex Jan 30 '18 at 19:16
  • The C# compiler is not able to do this. – Chris Pfohl Jan 31 '18 at 14:58
  • Not today, but it could in the future. There is a feature request for union types in C#: https://github.com/dotnet/csharplang/issues/399 – Alex Jan 31 '18 at 16:11
  • 1
    The thing is that C# generic parameter constraints and generics themselves are pretty primitive compared to, say, C++ templates. C# requires you to tell the compiler in advance what operations are allowed on generic types. The way to provide that info is to add an implements interface constraint (where T : IDisposable). But you may not want your type to implement some interface to use a generic method or you may want to allow some types in your generic code that don't have common interface. Ex. allow any struct or string so that you can simply call Equals(v1, v2) for value based comparison. – Vakhtang Aug 14 '18 at 12:06
  • Is there something I should add or remove from my answer here? I'm a bit confused by both the last two comments as they don't address the question ("why?"/"how?") at hand. I'm happy to adjust. – Chris Pfohl Aug 14 '18 at 12:37
13

If ChildClass means it is derived from ParentClass, you may just write the following to accept both ParentClass and ChildClass;

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

On the otherhand, if you want to use two different types with no inheritance relation between them, you should consider the types implementing the same interface;

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;
    }
}

then you can write your generic function as;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}
daryal
  • 14,643
  • 4
  • 38
  • 54
  • Good idea, except I don't think I'll be able to do this as I want to use string which is a sealed class as per a comment above... – Mansfield May 31 '12 at 12:56
  • this is a design problem in fact. a generic function is used to do similar operations with code reuse. both if you are planning to do different operations in the method body, seperating methods is a better way (IMHO). – daryal May 31 '12 at 13:17
  • What I'm doing, in fact, is writing a simple error logging function. I'd like that last parameter to either be a string of info about the error, or an exception, in which case I save e.message + e.stacktrace as a string anyway. – Mansfield May 31 '12 at 13:20
  • you may write a new class, having isSuccesful, save message and exception as properties. then you can check whether issuccesful true and do the remaining. – daryal May 31 '12 at 13:24
9

As old as this question is I still get random upvotes on my explanation above. The explanation still stands perfectly fine as it is, but I'm going to answer a second time with a type that's served me well as a substitute for union types (the strongly-typed answer to the question that's not directly supported by C# as is).

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");
            }
        }
    }
}

The above class represents a type that can be either TP or TA. You can use it as such (the types refer back to my original answer):

// ...
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());

Important Notes:

  • You'll get runtime errors if you don't check IsPrimary first.
  • You can check any of IsNeither IsPrimary or IsAlternate.
  • You can access the value through Primary and Alternate
  • There are implicit converters between TP/TA and Either<TP, TA> to allow you to pass either the values or an Either anywhere where one is expected. If you do pass an Either where a TA or TP is expected, but the Either contains the wrong type of value you'll get a runtime error.

I typically use this where I want a method to return either a result or an error. It really cleans up that style code. I also very occasionally (rarely) use this as a replacement for method overloads. Realistically this is a very poor substitute for such an overload.

OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31
Chris Pfohl
  • 18,220
  • 9
  • 68
  • 111
  • 1
    Based on this answer, I've created a `AnyOf` type which acts the same as this `Either` type but supports more types, up to 5. See https://github.com/StefH/AnyOf for details. – Stef Heyenrath Jul 04 '21 at 17:16