Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft EdgeMore info about Internet Explorer and Microsoft Edge
Table of contentsExit editor mode

Constraints on type parameters (C# Programming Guide)

Feedback

In this article

Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type. The compiler can only assume the members ofSystem.Object, which is the ultimate base class for any .NET type. For more information, seeWhy use constraints. If client code uses a type that doesn't satisfy a constraint, the compiler issues an error. Constraints are specified by using thewhere contextual keyword. The following table lists the various types of constraints:

ConstraintDescription
where T : structThe type argument must be a non-nullablevalue type, which includesrecord struct types. For information about nullable value types, seeNullable value types. Because all value types have an accessible parameterless constructor, either declared or implicit, thestruct constraint implies thenew() constraint and can't be combined with thenew() constraint. You can't combine thestruct constraint with theunmanaged constraint.
where T : classThe type argument must be a reference type. This constraint applies also to any class, interface, delegate, or array type. In a nullable context,T must be a non-nullable reference type.
where T : class?The type argument must be a reference type, either nullable or non-nullable. This constraint applies also to any class, interface, delegate, or array type, including records.
where T : notnullThe type argument must be a non-nullable type. The argument can be a non-nullable reference type or a non-nullable value type.
where T : unmanagedThe type argument must be a non-nullableunmanaged type. Theunmanaged constraint implies thestruct constraint and can't be combined with either thestruct ornew() constraints.
where T : new()The type argument must have a public parameterless constructor. When used together with other constraints, thenew() constraint must be specified last. Thenew() constraint can't be combined with thestruct andunmanaged constraints.
where T :<base class name>The type argument must be or derive from the specified base class. In a nullable context,T must be a non-nullable reference type derived from the specified base class.
where T :<base class name>?The type argument must be or derive from the specified base class. In a nullable context,T can be either a nullable or non-nullable type derived from the specified base class.
where T :<interface name>The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. In a nullable context,T must be a non-nullable type that implements the specified interface.
where T :<interface name>?The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. In a nullable context,T can be a nullable reference type, a non-nullable reference type, or a value type.T can't be a nullable value type.
where T : UThe type argument supplied forT must be or derive from the argument supplied forU. In a nullable context, ifU is a non-nullable reference type,T must be a non-nullable reference type. IfU is a nullable reference type,T can be either nullable or non-nullable.
where T : defaultThis constraint resolves the ambiguity when you need to specify an unconstrained type parameter when you override a method or provide an explicit interface implementation. Thedefault constraint implies the base method without either theclass orstruct constraint. For more information, see thedefault constraint spec proposal.
where T : allows ref structThis anti-constraint declares that the type argument forT can be aref struct type. The generic type or method must obey ref safety rules for any instance ofT because it might be aref struct.

Some constraints are mutually exclusive, and some constraints must be in a specified order:

  • You can apply at most one of thestruct,class,class?,notnull, andunmanaged constraints. If you supply any of these constraints, it must be the first constraint specified for that type parameter.
  • The base class constraint (where T : Base orwhere T : Base?) can't be combined with any of the constraintsstruct,class,class?,notnull, orunmanaged.
  • You can apply at most one base class constraint, in either form. If you want to support the nullable base type, useBase?.
  • You can't name both the non-nullable and nullable form of an interface as a constraint.
  • Thenew() constraint can't be combined with thestruct orunmanaged constraint. If you specify thenew() constraint, it must be the last constraint for that type parameter. Anti-constraints, if applicable, can follow thenew() constraint.
  • Thedefault constraint can be applied only on override or explicit interface implementations. It can't be combined with either thestruct orclass constraints.
  • Theallows ref struct anti-constraint can't be combined with theclass orclass? constraint.
  • Theallows ref struct anti-constraint must follow all constraints for that type parameter.

Why use constraints

Constraints specify the capabilities and expectations of a type parameter. Declaring those constraints means you can use the operations and method calls of the constraining type. You apply constraints to the type parameter when your generic class or method uses any operation on the generic members beyond simple assignment, which includes calling any methods not supported bySystem.Object. For example, the base class constraint tells the compiler that only objects of this type or derived from this type can replace that type argument. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. The following code example demonstrates the functionality you can add to theGenericList<T> class (inIntroduction to Generics) by applying a base class constraint.

public class Employee{    public Employee(string name, int id) => (Name, ID) = (name, id);    public string Name { get; set; }    public int ID { get; set; }}public class GenericList<T> where T : Employee{    private class Node    {        public Node(T t) => (Next, Data) = (null, t);        public Node? Next { get; set; }        public T Data { get; set; }    }    private Node? head;    public void AddHead(T t)    {        Node n = new(t) { Next = head };        head = n;    }    public IEnumerator<T> GetEnumerator()    {        Node? current = head;        while (current is not null)        {            yield return current.Data;            current = current.Next;        }    }    public T? FindFirstOccurrence(string s)    {        Node? current = head;        while (current is not null)        {            //The constraint enables access to the Name property.            if (current.Data.Name == s)            {                return current.Data;            }            else            {                current = current.Next;            }        }        return null;    }}

The constraint enables the generic class to use theEmployee.Name property. The constraint specifies that all items of typeT are guaranteed to be either anEmployee object or an object that inherits fromEmployee.

Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

class EmployeeList<T> where T : notnull, Employee, IComparable<T>, new(){    public void AddDefault()    {        T t = new();    }}

When applying thewhere T : class constraint, avoid the== and!= operators on the type parameter because these operators test for reference identity only, not for value equality. This behavior occurs even if these operators are overloaded in a type that is used as an argument. The following code illustrates this point; the output is false even though theString class overloads the== operator.

public static void OpEqualsTest<T>(T s, T t) where T : class{    Console.WriteLine(s == t);}private static void TestStringEquality(){    string s1 = "target";    System.Text.StringBuilder sb = new("target");    string s2 = sb.ToString();    OpEqualsTest(s1, s2);}

The compiler only knows thatT is a reference type at compile time and must use the default operators that are valid for all reference types. If you must test for value equality, apply thewhere T : IEquatable<T> orwhere T : IComparable<T> constraint and implement the interface in any class used to construct the generic class.

Constraining multiple parameters

You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:

class Base { }class Test<T, U>    where U : struct    where T : Base, new(){ }

Unbounded type parameters

Type parameters that have no constraints, such as T in public classSampleClass<T>{}, are called unbounded type parameters. Unbounded type parameters have the following rules:

  • The!= and== operators can't be used because there's no guarantee that the concrete type argument supports these operators.
  • They can be converted to and fromSystem.Object or explicitly converted to any interface type.
  • You can compare them tonull. If an unbounded parameter is compared tonull, the comparison always returns false if the type argument is a value type.

Type parameters as constraints

The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

public class List<T>{    public void Add<U>(List<U> items) where U : T {/*...*/}}

In the previous example,T is a type constraint in the context of theAdd method, and an unbounded type parameter in the context of theList class.

Type parameters can also be used as constraints in generic class definitions. The type parameter must be declared within the angle brackets together with any other type parameters:

//Type parameter V is used as a type constraint.public class SampleClass<T, U, V> where T : V { }

The usefulness of type parameters as constraints with generic classes is limited because the compiler can assume nothing about the type parameter except that it derives fromSystem.Object. Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

notnull constraint

You can use thenotnull constraint to specify that the type argument must be a non-nullable value type or non-nullable reference type. Unlike most other constraints, if a type argument violates thenotnull constraint, the compiler generates a warning instead of an error.

Thenotnull constraint has an effect only when used in a nullable context. If you add thenotnull constraint in a nullable oblivious context, the compiler doesn't generate any warnings or errors for violations of the constraint.

class constraint

Theclass constraint in a nullable context specifies that the type argument must be a non-nullable reference type. In a nullable context, when a type argument is a nullable reference type, the compiler generates a warning.

default constraint

The addition of nullable reference types complicates the use ofT? in a generic type or method.T? can be used with either thestruct orclass constraint, but one of them must be present. When theclass constraint was used,T? referred to the nullable reference type forT.T? can be used when neither constraint is applied. In that case,T? is interpreted asT? for value types and reference types. However, ifT is an instance ofNullable<T>,T? is the same asT. In other words, it doesn't becomeT??.

BecauseT? can now be used without either theclass orstruct constraint, ambiguities can arise in overrides or explicit interface implementations. In both those cases, the override doesn't include the constraints, but inherits them from the base class. When the base class doesn't apply either theclass orstruct constraint, derived classes need to somehow specify an override applies to the base method without either constraint. The derived method applies thedefault constraint. Thedefault constraint clarifiesneither theclass norstruct constraint.

Unmanaged constraint

You can use theunmanaged constraint to specify that the type parameter must be a non-nullableunmanaged type. Theunmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory, as shown in the following example:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged{    var size = sizeof(T);    var result = new byte[size];    byte* p = (byte*)&argument;    for (var i = 0; i < size; i++)        result[i] = *p++;    return result;}

The preceding method must be compiled in anunsafe context because it uses thesizeof operator on a type not known to be a built-in type. Without theunmanaged constraint, thesizeof operator is unavailable.

Theunmanaged constraint implies thestruct constraint and can't be combined with it. Because thestruct constraint implies thenew() constraint, theunmanaged constraint can't be combined with thenew() constraint as well.

Delegate constraints

You can useSystem.Delegate orSystem.MulticastDelegate as a base class constraint. The Common Language Runtime (CLR) always allowed this constraint, but the C# language disallowed it. TheSystem.Delegate constraint enables you to write code that works with delegates in a type-safe manner. The following code defines an extension method that combines two delegates provided they're the same type:

extension<TDelegate>(TDelegate source) where TDelegate : System.Delegate{    public TDelegate? TypeSafeCombine(TDelegate target)        => Delegate.Combine(source, target) as TDelegate;}

You can use the preceding method to combine delegates that are the same type:

Action first = () => Console.WriteLine("this");Action second = () => Console.WriteLine("that");var combined = first.TypeSafeCombine(second);combined!();Func<bool> test = () => true;// Combine signature ensures combined delegates must// have the same type.//var badCombined = first.TypeSafeCombine(test);

If you uncomment the last line, it doesn't compile. Bothfirst andtest are delegate types, but they're different delegate types.

Enum constraints

You can also specify theSystem.Enum type as a base class constraint. The CLR always allowed this constraint, but the C# language disallowed it. Generics usingSystem.Enum provide type-safe programming to cache results from using the static methods inSystem.Enum. The following sample finds all the valid values for an enum type, and then builds a dictionary that maps those values to its string representation.

extension<T>(T) where T : System.Enum{    public static Dictionary<int, string> EnumNamedValues()    {        var result = new Dictionary<int, string>();        var values = Enum.GetValues(typeof(T));        foreach (int item in values)            result.Add(item, Enum.GetName(typeof(T), item)!);        return result;    }}

Enum.GetValues andEnum.GetName use reflection, which has performance implications. You can callEnumNamedValues to build a collection that is cached and reused rather than repeating the calls that require reflection.

You could use it as shown in the following sample to create an enum and build a dictionary of its values and names:

enum Rainbow{    Red,    Orange,    Yellow,    Green,    Blue,    Indigo,    Violet}
var map = EnumNamedValues<Rainbow>();foreach (var pair in map)    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Type arguments implement declared interface

Some scenarios require that an argument supplied for a type parameter implement that interface. For example:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>{    static abstract T operator +(T left, T right);    static abstract T operator -(T left, T right);}

This pattern enables the C# compiler to determine the containing type for the overloaded operators, or anystatic virtual orstatic abstract method. It provides the syntax so that the addition and subtraction operators can be defined on a containing type. Without this constraint, the parameters and arguments would be required to be declared as the interface, rather than the type parameter:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>{    static abstract IAdditionSubtraction<T> operator +(        IAdditionSubtraction<T> left,        IAdditionSubtraction<T> right);    static abstract IAdditionSubtraction<T> operator -(        IAdditionSubtraction<T> left,        IAdditionSubtraction<T> right);}

The preceding syntax would require implementers to useexplicit interface implementation for those methods. Providing the extra constraint enables the interface to define the operators in terms of the type parameters. Types that implement the interface can implicitly implement the interface methods.

Allows ref struct

Theallows ref struct anti-constraint declares that the corresponding type argument can be aref struct type. Instances of that type parameter must obey the following rules:

  • It can't be boxed.
  • It participates inref safety rules.
  • Instances can't be used where aref struct type isn't allowed, such asstatic fields.
  • Instances can be marked with thescoped modifier.

Theallows ref struct clause isn't inherited. In the following code:

class SomeClass<T, S>    where T : allows ref struct    where S : T{    // etc}

The argument forS can't be aref struct becauseS doesn't have theallows ref struct clause.

A type parameter that has theallows ref struct clause can't be used as a type argument unless the corresponding type parameter also has theallows ref struct clause. This rule is demonstrated in the following example:

public class Allow<T> where T : allows ref struct{}public class Disallow<T>{}public class Example<T> where T : allows ref struct{    private Allow<T> fieldOne; // Allowed. T is allowed to be a ref struct    private Disallow<T> fieldTwo; // Error. T is not allowed to be a ref struct}

The preceding sample shows that a type argument that might be aref struct type can't be substituted for a type parameter that can't be aref struct type.

See also

Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, seeour contributor guide.

Feedback

Was this page helpful?

YesNoNo

Need help with this topic?

Want to try using Ask Learn to clarify or guide you through this topic?

Suggest a fix?

  • Last updated on

In this article

Was this page helpful?

YesNo
NoNeed help with this topic?

Want to try using Ask Learn to clarify or guide you through this topic?

Suggest a fix?