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.

el2iot2
  • 6,428
  • 7
  • 38
  • 51
  • 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. – Thanasis Ioannidis Dec 12 '20 at 18:49

6 Answers6

90

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    ah...that makes sense. Is the type name allowed to be the same if the type parameters provide a unique signature? – el2iot2 Apr 01 '09 at 23:56
  • 4
    @ee: yes, generics are `overloadable` by parameter count. – Mehrdad Afshari Apr 01 '09 at 23:56
  • @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. – Reed Copsey Apr 02 '09 at 00:01
  • @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. – Mehrdad Afshari Apr 02 '09 at 00:05
  • 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. – el2iot2 Apr 02 '09 at 00:14
  • @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, I'd rename it. – Reed Copsey Apr 02 '09 at 00:16
  • 4
    For example, Predicate is just Func, but renamed since it's for a different purpose. – Reed Copsey Apr 02 '09 at 00:17
  • IMO, The problem is that the compiler thinks this breaks Liskov substitution. You’ll get errors like “Cannot convert MyGeneric to type BaseGeneric” 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. – CZahrobsky Aug 29 '23 at 11:53
23

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>.

Thanasis Ioannidis
  • 2,981
  • 1
  • 30
  • 50
22

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>{}
Nerdroid
  • 13,398
  • 5
  • 58
  • 69
  • Please read other answers before you post a late answer, because probably your solution is the same as others. – Cheng Chen Dec 02 '15 at 03:05
  • 12
    the accepted answer was creating a class with different name, my solution is Overloading the same class – Nerdroid Dec 02 '15 at 03:17
  • 2
    No, both are creating a new class. The name doesn't matter here. `MyTemplate` is a different class from `MyTemplate`, neither `AnotherTemplate`. – Cheng Chen Dec 02 '15 at 03:20
  • Specifically, the type names in your example are `MyTemplate\`1` and `MyTemplate\`2`. As Danny Chen said, they are different classes. – JamesQMurphy Dec 22 '15 at 18:51
  • 10
    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. – Kuba Apr 21 '17 at 12:07
  • 3
    Unfortunately this solution is still inferior to true "default" generic arguments, because a `MyTemplate` cannot be assigned to `MyTemplate`, which can be desireable – Felk Oct 26 '18 at 12:33
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.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
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.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • 1
    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. – Mehrdad Afshari Apr 01 '09 at 23:59
  • 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. – Andrew Hare Apr 02 '09 at 00:03
  • @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 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 as shorthand for MyTemplate. – Tyler Laing Oct 14 '13 at 21:47
  • @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 class and then MyGeneric 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. – Tyler Laing Oct 14 '13 at 21:50
  • "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). – Mud Jul 15 '15 at 16:44
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> { }
mattylantz
  • 312
  • 4
  • 10