buddybubble
buddybubble

Reputation: 1319

Parameter constraints - call method to check type in constraint?

I have a method with a generic parameter:

internal void DoSomething<T>(T workWithThis)
{
}

I now want to constrain this method to only accept parameters which inherit one of a few interfaces I'd like to specify. However I have not yet found a way to it. What I'd like looks like this:

internal void DoSomething<T>(T workWithThis) where T : ISomething | ISomethingElse
{
}

Obviously this is not working, so I tried it with a static method to check the Type of T:

public static bool CheckType(Type t)
{
return */check here*/
}

internal void DoSomething<T>(T workWithThis) where T : CheckType(typeof(T))
{
}

Obviously this is not going to work either. The question is why? Why is the compiler preventing me from doing that, based on my understanding there is no reason for it not to work

Upvotes: 0

Views: 490

Answers (4)

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391276

The compiler has to verify all the constraints at compile time, and cannot call a method to do so.

The only things you can specify in the where constraints are:

  • new() - require a parameterless constructor
  • class - must be a reference type
  • struct - must be a value type
  • SomeBaseClass
  • ISomeInterface
  • T : U - must be, inherit from, or implement one of the other generic parameters

See the C# Programming Guide - Constraints on Type Parameters for more information.

As for why, you should never have to answer "I see no reason for this to work". You have to start in the opposite direction, "Why should this work", and then come up with enough plausible and realistic scenarios and requirements to make it worthwhile to implement. See Minus 100 Points by Eric Gunnerson.

To fix this in your code, you should derive both interfaces from a common interface, and add a constraint on that instead.

If the two interfaces have nothing in common, then I question the benefit of actually adding a constraint in the first place.

For instance, if your code is going to call a method on the objects being used with the generic type/method, then obviously both interfaces have to have the same notion about what that method is, and the only way to do that would be for the method to be defined in a common base interface. That the two interfaces happen to have the same method or property declared, with the same signature, does not make it the same method.

Having said that, are you sure you even need generics here?

How about just declaring two methods, each taking one such interface?

internal void DoSomething(ISomething workWithThis)
internal void DoSomething(ISomethingElse workWithThis)

Upvotes: 2

Vyacheslav Volkov
Vyacheslav Volkov

Reputation: 4742

The compiler uses the generic constraint to determine what operations is available on T within the generic method - so allowing an or expression would not be type safe. For example you have two interfaces IFirst and ISecond:

public interface IFirst
{
    void First();
}

public interface ISecond
{
    void Second();
}
internal void DoSomething<T>(T workWithThis) where T : IFirst or ISecond
{
    //How to call this method if the type is ISecond
    workWithThis.First();
    //How to call this method if the type is IFirst
    workWithThis.Second();
}

Upvotes: 1

Tigran
Tigran

Reputation: 62248

You can define an empty interface that holds all of them.

Remember, that in C# interfaces can have multiple inheritance.

For example:

public interface IHolder : ISomething, ISomethingElse 
{

}

and for generic

internal void DoSomething<T>(T workWithThis) where T : IHolder
{
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499770

Why is the compiler preventing me from doing that, based on my understanding there is no reason for it not to work

The compiler is preventing you from doing it because you're trying to do something which isn't supported by C# as a language. The syntax you're trying to use does not comply with the productions in section 10.1.5 of the C# spec.

C# as a language simply does not support the scenario you require.

Now as for why the language doesn't allow this sort of flexibility - that comes down to the normal balancing act of:

  • How many developers would benefit from this and how much
  • Extra burden on other developers to understand a more complicated language
  • Resources (primarily within Microsoft) required to design the language feature, implement and test it

Oh, and of course this isn't just C# - the CLR would have to support such a restriction as well, and it would at least encourage other CLR languages to understand it too.

I suggest you solve this by having two separate methods. Note that they can't just be overloads of generic methods, as overloads cannot differ only by generic type constraints. If you don't mind about boxing for value types implementing the interface, you could overload with:

internal void DoSomething(ISomething something)
{
}

internal void DoSomething(ISomethingElse somethingElse)
{
}

... although then if you pass in a value where the expression is a type implementing both interfaces, you'll end up with overload ambiguity.

Alternatively, just give the two methods different names.

Upvotes: 5

Related Questions