Skip to content

Conversation

BillWagner
Copy link
Member

@BillWagner BillWagner commented Apr 18, 2018

Describe the new generic constraints on unmanaged, using System.Enum and System.Delegate as base classes.

This PR relies on the new samples in dotnet/samples#19

/cc @OmarTawfik @jcouv

Note: The build will fail until the new samples are merged.

BillWagner and others added 3 commits April 12, 2018 11:35
The correct descriptions are now in place.
Sweep markdown files with Markdown lint.
MOve all snippets to the snippets folders in the samples directory.
```


In a generic type definition, the `where` clause is used to specify constraints on the types that can be used as arguments for a type parameter defined in a generic declaration. For example, you can declare a generic class, `MyGenericClass`, such that the type parameter `T` implements the <xref:System.IComparable%601> interface:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though the first sentence is from the current edition of the docs, can we rephrase it? In a generic type definition... sets scope directly to generic types, omitting methods and delegates (though, these two are mentioned at the end of the article). What about:

The where clause is used to specify constraints on the types that can be used as arguments for a type parameter defined in a declaration of a generic type, method, or delegate.

[!code-csharp[using an interface constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#2)]

The `where` clause can specify that the type is a `class` or a `struct`. The `struct` constraint removes the need to specify a base class constraint of `System.ValueType`. The `System.ValueType` type may not be used as
a base class constraint. The following example shouws both the `class` and `struct` constraints:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo at the last sentence: The following example shouws...


[!code-csharp[using the class and struct constraints](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#3)]

The `where` clause may also include an `unmanaged` constraint. The unmanaged
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second sentence should use code formatting for "unmanaged": The unmanaged constraint


The `where` clause may also include an `unmanaged` constraint. The unmanaged
constraint limits the type parameter to types known as **unmanaged types**.
An **unmanaged type** is a type which is not a reference type and doesn't
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which -> that


[!code-csharp[using the unmanaged constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#4)]

The `where` clause may also include a constructor constraint. It is possible to create an instance of a type parameter using the new operator; however, in order to do so the type parameter must be constrained by the constructor constraint, `new()`. The [new() Constraint](new-constraint.md) lets the compiler know that any type argument supplied must have an accessible parameterless--or default-- constructor. For example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about to shorten the first two sentences into:

The where clause may also include a constructor constraint, new(). That constraint makes it possible to create an instance of a type parameter using the new operator.

?


## Why Use Constraints

If you want to examine an item in a generic list to determine whether it is valid or to compare it to some other item, the compiler must have some guarantee that the operator or method it has to call will be supported by any type argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. Constraints are applied by using the contextual keyword `where`. The following code example demonstrates the functionality we can add to the `GenericList<T>` class (in [Introduction to Generics](introduction-to-generics.md)) by applying a base class constraint.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sentence at the end of the paragraph

Constraints are applied by using the contextual keyword where

is repeated from the first paragraph.

The first sentence is not very clear. (Though, it's not in the scope of this PR.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworked both the introduction and the Why use Constraints opening paragraph.


[!code-csharp[using the class and struct constraints](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#10)]

By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. Therefore, when you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by `System.Object`, you will have to apply constraints to the type parameter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remark: this paragraph fits better to be the first one in the Why Use Constraints section


[!code-csharp[using the unmanaged constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#15)]

The preceding method must be compiled in an `unsafe` context because it uses the `sizeof` operator on a type not known to be a builtin type. Without the `unmanaged` constraint, the `sizeof` operator would be unavailable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: builtin


## Delegate constraints

Also beginning with C# 7.3, you can use `System.Delegate` or `System.MulticastDelegate` as a base class constraint. The CLR always allowed this, but the C# language disallowed it. The `System.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 are the same type:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second sentence is an interesting fact, but I'm not sure it adds information that makes using constraints easier. Should we remove it, and, if not, then to add the same remark for enum constraints?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it in both places. It clarifies that a new CLR is not needed.


## Enum constraints

Beginning in C# 7.3, you can also specify the `System.Enum` type as a base class constraint. This enables type safe programming to cache results from using the static methods in `System.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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use xref-link in the second sentence: <xref:System.Enum>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And added xrefs for System.Delegate


In addition to interface constraints, a `where` clause can include a base class constraint, which states that a type must have the specified class as a base class (or be that class itself) in order to be used as a type argument for that generic type. If such a constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint. These restrictions were relaxed beginning with C# 7.3. The following examples shows the types that can now be specified as a base class:

[!code-csharp[using an interface constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#2)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericWhereConstraints.cs [](start = 90, length = 26)

Was GenericWhereConstraints.cs updated as part of this PR? I don't see it in the file list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See dotnet/samples#19 for the code files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks


[!code-csharp[using the class and struct constraints](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#3)]

The `where` clause may also include an `unmanaged` constraint. The unmanaged
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the line breaks for this paragraph intentional?
Sentences above (ex. line 25) fit on one line, but here some sentences contain line breaks.


[!code-csharp[using the new constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#5)]

The `new()` constraint appears last in the `where` clause. The `new()` constraint cannot be combined with the `struct` or `unmanaged` constraints. All types satisfying those constraints must have an accessible parameterless constructor, making the `new()` constraint redundant.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double space before "The". Is that intentional?

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 3).
Main concern is possibly missing file in PR.

@BillWagner
Copy link
Member Author

@jcouv thanks for the review. The code samples have all moved to a new repo: dotnet/samples The changes for this PR are in dotnet/samples#19

Also, run Acrolinx for style issues.

## Why Use Constraints

If you want to examine an item in a generic list to determine whether it is valid or to compare it to some other item, the compiler must have some guarantee that the operator or method it has to call will be supported by any type argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. Constraints are applied by using the contextual keyword `where`. The following code example demonstrates the functionality we can add to the `GenericList<T>` class (in [Introduction to Generics](introduction-to-generics.md)) by applying a base class constraint.
By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. When you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by `System.Object`, you will have to apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. 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 the `GenericList<T>` class (in [Introduction to Generics](introduction-to-generics.md)) by applying a base class constraint.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's xref System.Object, so folks can look up which methods are supported by unconstrained types.


## Unmanaged constraint

Beginning with C# 7.3, you can use the `unmanaged` constraint to specify that the type parameter must be an **unmanaged types**. An **unmanaged type** is a type which is not a reference type and doesn't contain reference type fields at any level of nesting. The `unmanaged` 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:
Beginning with C# 7.3, you can use the `unmanaged` constraint to specify that the type parameter must be an unmanaged type**. An **unmanaged type** is a type that is not a reference type and doesn't contain reference type fields at any level of nesting. The `unmanaged` 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo at the end of the first sentence: two stars should be removed.

@@ -99,11 +97,11 @@ If you uncomment the last line, it won't compile. Both `first` and `test` are de

## Enum constraints

Beginning in C# 7.3, you can also specify the `System.Enum` type as a base class constraint. This enables type safe programming to cache results from using the static methods in `System.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.
Beginning in C# 7.3, you can also specify the <xref:System.Enum?displayProperty=nameWithType> type as a base class constraint. Generics using `System.Enum` provide type safe programming to cache results from using the static methods in `System.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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the CLR remark for the System.Enum constraint. Sorry, if I'm too fast, and it comes in the next commit.


[!code-csharp[using the unmanaged constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#4)]

The `where` clause may also include a constructor constraint. It is possible to create an instance of a type parameter using the new operator; however, in order to do so the type parameter must be constrained by the constructor constraint, `new()`. The [new() Constraint](new-constraint.md) lets the compiler know that any type argument supplied must have an accessible parameterless--or default-- constructor. For example:
The `where` clause may also include a constructor constraint, `new()`. That constraint makes it possible to create an instance of a type parameter using the new operator. The [new() Constraint](new-constraint.md) lets the compiler know that any type argument supplied must have an accessible parameterless--or default-- constructor. For example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: code-style (backticks) in the second sentence for the operator name: ...using the new operator

@OmarTawfik
Copy link

Maybe we can mention that you can apply constraints on local functions here?

using System;
public class C {
    public void M() {
        void N<T>(T arg) where T : unmanaged {
        }
        
        N(5);			// valid
        N("test");		// invalid
    }
}

@BillWagner
Copy link
Member Author

@OmarTawfik That's a good point. I made one small update to this topic, and wrote #5018 to add a full treatment of generic local functions in the future.

pkulikov referenced this pull request in dotnet/samples Apr 24, 2018
* import existing snippets

This removes them from the sub-directories in the docs repository.

* finish generics samples.

* respond to feedback.

simplify an empty declaration.
BillWagner added a commit to BillWagner/samples that referenced this pull request Apr 24, 2018
BillWagner added a commit to dotnet/samples that referenced this pull request Apr 24, 2018
@BillWagner
Copy link
Member Author

closing and reopening to force a build

@BillWagner BillWagner closed this Apr 24, 2018
@BillWagner BillWagner reopened this Apr 24, 2018
@BillWagner BillWagner requested a review from rpetrusha April 25, 2018 14:41
Copy link
Contributor

@rpetrusha rpetrusha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that I've taken so long to review this, @BillWagner. I've left a number of comments and suggestions.

@@ -17,7 +17,7 @@ ms.author: "wiwagn"
# Compiler Error CS0702
Constraint cannot be special class 'identifier'

The following types may not be used as constraints: `System.Object,``System.Array`, `System.Delegate`, `System.Enum`, or `System.ValueType`.
The following types may not be used as constraints: `System.Object,``System.Array`, or `System.ValueType`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a formatting problem here with the fence following rather than preceding the comma. But why not make these xrefs: <xref:System.Object>, <xref:System.Array>, or <xref:System.ValueType>?

## See Also
> For more information on the where clause in a query expression, see [where clause](where-clause.md).

The `where` clause can also include a base class constraint. The base class constraint states that a type has the specified class as a base class (or is that class) to be used as a type argument for that generic type. If the base class constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint. These restrictions were relaxed beginning with C# 7.3. The following example shows the types that can now be specified as a base class:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • better: "...a type to be used as a type argument for that generic type has the specified class as a base class (or is that base class)..."
  • Some types are disallowed -- can they be listed?
  • "the types that can now be specified as a base class" -- this can be interpreted to mean that these are the only types that can now be used in the base class constraint.

```


The `where` clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. For example, you can declare a generic class, `MyGenericClass`, such that the type parameter `T` implements the <xref:System.IComparable%601> interface:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I'd add a summary sentence after the first sentence. Something like, "Constraints can specify interfaces, base classes, classes. structs or unmanaged types..."
Then I'd also make the interface constraint a separate paragraph, since the interface constraint is one of the types of constraints. Here it just appears to be an example of some kind of constraint.

## See Also
<xref:System.Collections.Generic>

Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the compiler can only assume the members of <xref:System.Object?displayPropety=nameWithType>. If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. Constraints are specified by using the `where` contextual keyword. The following table lists the seven types of constraints:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mention of only assuming System.Object seems somewhat mysterious; I think I'd add a parenthetical "(For more information, see [Why use constraints](#why-use-constraints).


Some of the constraints are mutually exclusive. All value types must have an accessible parameterless constructor. The `struct` constraint implies the `new()` constraint and the `new()` constraint cannot be combined with the `struct` constraint. The `unmanaged` constraint implies the `struct` constraint. The `unmanaged` constraint cannot be combined with either the `struct` or `new()` constraints.

## Why Use Constraints
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heads should be sentence-cased


- The `!=` and `==` operators cannot be used because there is no guarantee that the concrete type argument will support these operators.
- They can be converted to and from `System.Object` or explicitly converted to any interface type.
- You can compare to [null](../../language-reference/keywords/null.md). If an unbounded parameter is compared to `null`, the comparison will always return false if the type argument is a value type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare to --> compare them to


[!code-csharp[using the unmanaged constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#15)]

The preceding method must be compiled in an `unsafe` context because it uses the `sizeof` operator on a type not known to be a built-in type. Without the `unmanaged` constraint, the `sizeof` operator would be unavailable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be --> is


## Delegate constraints

Also beginning with C# 7.3, you can use <xref:System.Delegate?displayProperty=nameWithType> or <xref:System.MulticastDelegate?displayProperty=nameWithType> as a base class constraint. The CLR always allowed this constraint, but the C# language disallowed it. The `System.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 are the same type:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: type safe --> type-safe


## Enum constraints

Beginning in C# 7.3, you can also specify the <xref:System.Enum?displayProperty=nameWithType> type as a base class constraint. The CLR always allowed this constraint, but the C# language disallowed it. Generics using `System.Enum` provide type safe programming to cache results from using the static methods in `System.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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: type safe --> type-safe


[!code-csharp[using the unmanaged constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#18)]

The methods used make use of reflection, which has performance implications. You can call this method build a collection that would be cached and reused rather than repeating the calls that require reflection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be --> is

@BillWagner
Copy link
Member Author

Thanks @rpetrusha I've addressed all your comments.

@@ -15,14 +15,16 @@ ms.author: "wiwagn"
---
# where (generic type constraint) (C# Reference)

The `where` clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. For example, you can declare a generic class, `MyGenericClass`, such that the type parameter `T` implements the <xref:System.IComparable%601> interface:
The `where` clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, classes. structs or unmanaged types. They declare capabilities that the type argument must possess.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the pre-last sentence: should be a comma between classes and structs.

However, that sentence reads strange: Constraints can specify ... base classes, classes .... First, I though it's a typo, because base classes are just mentioned before in the sentence. Then I realized we don't specify a certain class but force a generic type to be a reference type. So, what about such variant:

Constraints can specify interfaces, base classes, or require a generic type to be a reference, value or unmanaged type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion!


[!code-csharp[using an interface constraint](../../../../samples/snippets/csharp/keywords/GenericWhereConstraints.cs#1)]

> [!NOTE]
> For more information on the where clause in a query expression, see [where clause](where-clause.md).

The `where` clause can also include a base class constraint. The base class constraint states that a type has the specified class as a base class (or is that class) to be used as a type argument for that generic type. If the base class constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint. These restrictions were relaxed beginning with C# 7.3. The following example shows the types that can now be specified as a base class:
The `where` clause can also include a base class constraint. The base class constraint states that a type to be used as a type argument for that generic type has the specified class as a base class (or is that base class) to be used as a type argument for that generic type. If the base class constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint: <xref:System.Object>, <xref:System.Array>, and <xref:System.ValueType>. Prior to C# 73, <xref:System.Enum>, <xref:System.Delegate>, and <xref:System.MulticastDelegate> were also disallowed as base class constraints. The following example shows the types that can now be specified as a base class:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: C# 73 -> C# 7.3

@@ -29,7 +29,7 @@ Constraints inform the compiler about the capabilities a type argument must have

Some of the constraints are mutually exclusive. All value types must have an accessible parameterless constructor. The `struct` constraint implies the `new()` constraint and the `new()` constraint cannot be combined with the `struct` constraint. The `unmanaged` constraint implies the `struct` constraint. The `unmanaged` constraint cannot be combined with either the `struct` or `new()` constraints.

## Why Use Constraints
## Why use Constraints
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lower-case all headings in this topic for consistency with Constraints -> constraints.
For example, other two existing headings are Enum constraints and Delegate constraints

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked all the other headings in the PR for sentence case as well.

Copy link
Contributor

@rpetrusha rpetrusha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good, @BillWagner. You can merge when you're ready.

@BillWagner BillWagner merged commit 87dc518 into dotnet:master Apr 26, 2018
@BillWagner BillWagner deleted the csharp73-new-generic-constraints branch May 23, 2018 15:13
karelz pushed a commit to karelz/samples that referenced this pull request Aug 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants