In C#, generic methods can specify the type of data they take as parameters, or return. They take their type parameters whenever they are called. The type parameter can be used as the return type, or for the parameter, or both.
This is distinctly different from a method in a generic class that uses the class’s own type parameters. In contrast, a generic method requires its type parameter to be specified whenever the method is called. This means that, in any class, a generic method can use a different data type whenever it is called.
// Declaring a generic method.T GenericMethod<T> (T value) { return value; }// Calling a generic method.int x = GenericMethod<int> (10);
In C#, constraints are used to restrict the types that can be used as arguments for a generic type parameter. They are declared with the where
keyword after specifying the type parameter: class myClass<T> where T : constraints.
// define a generic class with a constraint.class GenericClass<T> where T : class { }// define a generic interface with a constraint.interface IGenericInterface<T> where T : struct { }// define a generic method with a constraint.T GenericMethod<T> where T : new() (T value) { }
In C#, covariance and contravariance allow for implicit reference conversion for generic type parameters.
Covariance means we can use a more derived type than originally specified. If the Derived class is derived from Base, then an instance of Interface<Derived>
can be assigned to a variable of type Interface<Base>
. Covariant type parameters are used only in an interface’s return types.
Contravariance means we can use a less derived type than originally specified. With the same classes above, this would mean we can assign Interface<Base>
to a variable of type Interface<Derived>
. Contravariant type parameters are only used in an interface’s parameter types.
// Interface with contravariant type parameter.interface ISerializer<in T> { string GetJSON (T input); }// Interface with a covariant type parameter.interface IDeserializer<out T> { T ReadJSON (string input); }// If DerivedClass is derived from BaseClass, the following are legal:ISerializer<DerivedClass> v1 = new ISerializer<BaseClass>()IDeserializer<BaseClass> v2 = new IDeserializer<DerivedClass>()
In C#, you can apply multiple constraints to a single generic type parameter by combining various types of constraints. Constraints are separated by commas after the where T :
.
// Declaring a class with multiple constraints.class GenericClass<T> where T : class, IComparable<T>, new() { }
class
ConstraintIn C#, the class
constraint specifies that the type argument must be a reference type, such as a class, interface, delegate or array type. Trying to insatiate the generic class, interface, or method with some other type will throw an exception.
// Defining a class with the class constraint.class GenericClass<T> where T : class { }
struct
ConstraintIn C#, the struct
constraint specifies that the type argument must be a value type, such as bool
, int
, or a struct
. Trying to instantiate the generic class, interface, or method with some other type will cause an exception.
// Declaring a class with a struct constraint.class GenericClass<T> where T : struct { }
new()
ConstraintIn C#, the new()
constraint specifies that the type argument must have a parameterless constructor. If you attempt to instantiate the generic class, interface, or method with a type whose constructor requires parameters, it will throw an exception.
// Declaring a class with a new() constraint.class GenericClass<T> where T : new() { }
In C#, a base class constraint ensures that the type argument inherits from a specific base class. If you attempt to instantiate the generic class, interface, or method with a type not derived from the base class, it will throw an exception.
// Declaring a class with a base class constraint.class GenericClass<T> where T : List { }
In C#, an interface constraint ensures that the type argument implements a specific interface. If you try to instantiate the generic class, interface, or method with a type that is not derived from the interface, it will throw an exception.
// Defining a class with an interface constraint.class GenericClass<T> where T : IList { }
In C# when using multiple type constraints there are some limitations:
struct
and class
constraints must be first.new()
must be last.struct
and new()
constraints can’t be combined.// Legal combinations of constraints.class GenericClass1<T> where T : class, new() { }class GenericClass2<T> where T : IComparable<T>, IEquatable <T>, new() { }class GenericClass3<T> where T : struct, IEquatable <T> { }class GenericClass4<T> where T : List, new() { }// Illegal combinations of constraintsclass BadGenericClass1<T> where T : List, List<T> { }class BadGenericClass2<T> where T : new(), IComparable<T>, IEquatable <T> { }class BadGenericClass3<T> where T : struct, new() { }
In C#, generics enable the creation of classes, methods, and interfaces that can operate with any data type. They are defined with a type parameter and are instantiated with a specific data type.
// Defining a genric classclass GenericClass<T> { }// Instantiating a generic classGenericClass<int> x = new GenericClass<int>;
In C#, generics are often used for collection classes and interfaces. The System.Collections.Generic
namespace offers many built in classes and interfaces for dealing with generic collections. Examples include:
List<T>
Dictionary<TKey,TValue>
Queue<T>
Stack<T>
SortedList<TKey,TValue>
List<int> myList = new Lint<int>();int x = 10;myList.Add(x);
In C#, generic type parameters are specified using angle brackets (<...>
). The type parameter goes after the name of the generic class, interface, or method.
// Defining a genric classclass GenericClass<T> { }// Instantiating a generic classGenericClass<int> x = new GenericClass<int>;
Generic classes in C# ensure type safety by allowing developers to define the specific data types they operate on. This approach minimizes runtime errors and enhances code reusability. Generics serve as powerful tools for creating flexible and type-safe code structures, accommodating various data types while reducing redundancy.
In C#, generic methods can be declared within non-generic classes. Because the method declares its own type parameters, it can be defined within any class, not just a generic class.
// A non-generic class with generic methods.class SerializeMe {public static string GetJSON<T> (T input) { /* serialization code goes here */ };public static T ReadJSON<T> (string input) { /* deserialization code goes here */ };}
The default
keyword in C# is used to return a default value of a parameterized type. This is useful when it’s unknown if the type parameter could be a value type or a reference type.
// Example, returning a default value when an error is thown.Public T ReadJSON(string input) {try {/* deserialization code here */}catch (Exception e) {return default(T);}}
In C#, generic interfaces define methods and properties that can operate on different data types. They are defined just like generic classes and use the type parameter for method return types and parameters. They can be implemented in a generic class or implemented in a non-generic class when the type parameter is specified.
// Define a generic interface.interface IGenericInterface<T> {T ReturnValue(T value);}// Implemented in a generic class.class myGenericClass<T> : IGenericInterface<T> {T ReturnValue(T value) { return value; }}// Implemented in a non-generic classclass myNonGenericClass : IGenericInterface<int> {int ReturnValue(int value) { return value; }}