views:

306

answers:

4

Is it possible to constrain a genericised method to accept only specific types of struct?

This is OK I believe:

string Add<T>(object value, T expiration) where T : struct;

but this isn't it appears:

string Add<T>(object value, T expiration) where T : Struct1, Struct2;

Note: the structs I wish to constrain it to are DateTime or TimeSpan and hence I have no control over them.

Thanks

A: 

I think both are not OK, a struct is not a valid constraint. A type used as a constraint must be interface, non sealed or a type parameter

Ngu Soon Hui
http://msdn.microsoft.com/en-us/library/d5x73970.aspx indicates the former is OK
Ben Aston
Indeed, there are other constraints beyond types: "new()" "struct" or "class".
Jon Skeet
+4  A: 

No, as a struct is sealed (you can't create a subclass of a ValueType).

Instead, consider having your structs implement an interface, then use that as a constraint, like so:

string Add<T>(object value, T expiration) where T : struct, IMyInterface

Jeremy McGee
I agree that this should be achieved with interfaces. This is one of the purposes of an interface!
Henri
Thanks for your answer - please can you explain the significance of not being able to subclass the type? Constraining the parameter to structs (value types) appears possible and contradictory to your statement. The structs I wish to constrain it to are DateTime or TimeSpan, so wrapping these in an interface would seem to be the only solution (or leave un genericised).
Ben Aston
You can't enforce a new interface on a struct you don't control (`DateTime`, `TimeSpan` etc), so no: you can't do this.
Marc Gravell
He means you can't derive from a value type (int, struct, double). You can constrain it to all structs, but you can't constrain it to particular structs.
Alastair Pitts
If you need to use this for classes or struct that you do not control you should make people aware of that. Using the name "MyStruct" in your question is misleading.
Ed Swangren
You'd need to wrap (decorate) the DateTime or TimeSpan in a custom struct or class if you wanted to do this. In practice it sounds like having two overloads of Add() would be better here: Add(object value, TimeSpan expiration) and Add(object value, DateTime expiration). Your code will be different in both cases anyway...
Jeremy McGee
I apologise for the misleading question.
Ben Aston
+3  A: 

I expect an overload is your best option here:

string Add(object value, MyStruct1 expiration) {...}
string Add(object value, MyStruct2 expiration) {...}

This is even more appropriate since you can't subclass a struct, thus the only viable T in your example would be MyStruct1 and MyStruct2 - may as well have specific methods, then.

Re restricting generics to multiple cited types; not really - and even if there were, the name "Add" suggests you'd want to use operator support, which also isn't in C# (3.0).

However, in C# 4.0, dynamic might be an option here - this acts as an optimised form of duck-typing; you wouldn't get compiler support (validation etc), but it should work. You would cast to dynamic inside the method:

string Add<T>(object value, T expiration) where T : struct {
    dynamic valueDyn = value;
    valueDyn += expiration; // or similar
    // more code
}

Another option (in .NET 3.5) is to use the Operator support in MiscUtil, using Operator.Add or Operator.AddAlternative.

Marc Gravell
+3  A: 

The purpose of generics is to make methods and types that are generic, hence the name. If you only have two possible type arguments, then just write two methods.

Remember also, C# generics are not C++ templates. When you call a method on a type parameter, say, that method call will be the exact same method call for every construction of the type parameter. It's generic. It's not a template where the compiler compiles the code two, three, a thousand times, one for each type argument, and works out afresh for each one what the method call is.

So even if you could restrict your type argument to two types, what good would that do you? All you could call on them was the methods they had in common via their base class, Object. In which case, you've got a generic method that works on all objects, so why restrict it to two types?

Eric Lippert