Is there a reasonable approach to "default" type parameters in C# Generics?
Asked Answered
A

6

107

In C++ templates, one can specify that a certain type parameter is a default. I.e. unless explicitly specified, it will use type T.

Can this be done or approximated in C#?

I'm looking for something like:

public class MyTemplate<T1, T2=string> {}

So that an instance of the type that doesn't explicitly specify T2:

MyTemplate<int> t = new MyTemplate<int>();

Would be essentially:

MyTemplate<int, string> t = new MyTemplate<int, string>();

Ultimately I am looking at a case wherein there is a template that is fairly widely used, but I am considering expanding with an additional type parameter. I could subclass, I guess, but I was curious if there were other options in this vein.

Autobiography answered 1/4, 2009 at 23:51 Comment(1)
I think the factory mathods pattern I answered is better in your situation. The only drawback is that you need to instantiate the objects through the static factory methods instead of directly using constructors. But this solution leads you to have only one class instead of two.Biff
C
91

Subclassing is the best option.

I would subclass your main generic class:

class BaseGeneric<T,U>

with a specific class

class MyGeneric<T> : BaseGeneric<T, string>

This makes it easy to keep your logic in one place (the base class), but also easy to provide both usage options. Depending on the class, there is probably very little extra work needed to make this happen.

Cymry answered 1/4, 2009 at 23:53 Comment(8)
ah...that makes sense. Is the type name allowed to be the same if the type parameters provide a unique signature?Autobiography
@ee: yes, generics are overloadable by parameter count.Chomp
@ee: Yes, but I would be wary of doing that. It is "legal" in .NET to do so, but it can lead to confusion. I would rather have the string derived type's name be similar to the main generic class (so it's obvious what it is/easy to find), but a name that makes it obvious that it's a string.Cymry
@Reed: Does it really lead to confusion? For this specific case, I think having the same name even helps. There are examples in .NET that do the same thing: Func<> delegate for instance.Chomp
I can see it both ways. If you expected overloaded versions to be equally used, I think I would use the same type name. If it were that the underlying base was rarely used but for special circumstances, I think choosing a name that portraits that would make sense. Thanks all for the quality answers.Autobiography
@Mehrdad: It depends on the usage scenario, etc. Func<>/Action<>/etc are a bit different - They aren't providing defaults options with a specific type, but acting more like method overloads since the extra arguments are completely unused. If I wanted a typedef for Func<T,U,string>, I'd rename it.Cymry
For example, Predicate<T> is just Func<T,bool>, but renamed since it's for a different purpose.Cymry
IMO, The problem is that the compiler thinks this breaks Liskov substitution. You’ll get errors like “Cannot convert MyGeneric<T> to type BaseGeneric<T, string>” because no implicit conversion exists yet in .NET Framework (at least through 4.6.1) and you cannot define an implicit conversion between a base class and a superclass. I’ve learned it’s better to favor composition over inheritance and skip inheriting generics, at least off of a Base class. To avoid this, especially with ORM like EntityFramework, you can use names like MySubclassWithString and pass it as a base.Siclari
B
24

One solution is subclassing. Another one I would use instead, is factory methods (combined with var keyword).

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

In the above example val2 is of type MyTemplate<int,string> and not a type derived from it.

A type class MyStringTemplate<T>:MyTemplate<T,string> is not the same type as MyTemplate<T,string>. This could pose some problems in certain scenarios. For instance you can't cast an instance of MyTemplate<T,string> to MyStringTemplate<T>.

Biff answered 15/12, 2014 at 16:34 Comment(1)
This is the most usable approach. Very nice solutionTallow
S
23

you can also create a class Overload like so

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Satiate answered 2/12, 2015 at 2:23 Comment(6)
Please read other answers before you post a late answer, because probably your solution is the same as others.Show
the accepted answer was creating a class with different name, my solution is Overloading the same classSatiate
No, both are creating a new class. The name doesn't matter here. MyTemplate<T1> is a different class from MyTemplate<T1, T2>, neither AnotherTemplate<T1>.Show
Specifically, the type names in your example are MyTemplate`1 and MyTemplate`2. As Danny Chen said, they are different classes.Shiloh
Even if the real types are different, which is clear and correct, the coding is easier if you keep same name. It is not obvious for everyone that class "overload" can be used that way. @DannyChen is right - from technical point of view results are the same, but this answear is closer to achieve what OP asked for.Whalen
Unfortunately this solution is still inferior to true "default" generic arguments, because a MyTemplate<T1, string> cannot be assigned to MyTemplate<T1>, which can be desireablePurpurin
C
12

C# does not support such a feature.

As you said, you can subclass it (if it's not sealed, and duplicate all constructor declarations) but it's a completely different thing.

Chomp answered 1/4, 2009 at 23:54 Comment(0)
S
3

Unfortunately C# does not support what you are trying to do. It would be a difficult feature to implement given that the default type for a parameter would have to adhere to the generic constraints and would most likely create headaches when the CLR tried to ensure type-safety.

Shipment answered 1/4, 2009 at 23:57 Comment(5)
Not really. It could have been done with an attribute (like default parameters in VB.NET) and have the compiler replace it at compile time. The primary reason is C# design goals.Chomp
The compiler has to ensure that the default parameter satisfies the generic constraints. Also the default parameter would be a generic constraint itself because any assumptions made about the type parameter in the method would require that any non-default type parameter inherit from it.Shipment
@Andrew, the default parameter need not be a generic constraint. If this were to behave more like default template parameters in C++ then extending upon automatonic's class it would be perfectly fine do: MyTemplate<int, float> x = null Because T2 has no generic constraints, so float is fine despite the default type of string. In this way default template parameters are essentially just "syntactical sugar" for writing MyTemplate<int> as shorthand for MyTemplate<int, string>.Overarm
@Andrew, I do agree that the C# compiler should verify that such default template parameters satisfy existing generic conststaints. The C# compiler already verifies something similiar in class declarations, e.g. add a generic constraint such as "where U : ISomeInterface" to Reed's BaseGeneric<T,U> class and then MyGeneric<T> will fail to compile with an error. Verifying a default template parameter would be very much the same: compiler verifies upon the declaration of a class and the error message can be the same or very similar.Overarm
"It would be a difficult feature to implement" That's nonsense. It would be trivial to implement. The job of validating a type against the template constraints doesn't get harder because the candidate type appears in a different place in the file (i.e. in the template rather than at template instantiation).Yogi
M
2

If you want to overload the type to the interface, you can do this: Example:

public interface ISRD<TItem, TId>
{
    Task SaveAsync(TItem item);
    Task<TItem> GetAsync(TId id);
    Task DeleteAsync(TItem item);
}

public interface ISRD<TItem>: ISRD<TItem, Guid> { }
Manns answered 18/1, 2022 at 0:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.