Topics on Generics
Topics on Generics
Constraints in a Generic Class
Constraining a Parameter to a Structure
When creating a generic class, we saw that you can indicate that it would use a parameter type but you are not specifying the type of that parameter. You can put a restriction to indicate how the compiler should deal with the parameter. You have many options.
To create a constraint on a generic class, after the <type-name> operator, type where TypeName : followed by the rule that the class must follow. The basic formula to create a generic restriction is:
class class-name<parameter-type>
where T : constraint rule(s)
{
}
As we will see, there are various types of constraints you can apply to generic classes.
Constraining a Parameter to a Structure Type
When creating a generic class, you can indicate that you want the parameter type to be a structure. To do this, set the constraint rule to struct. Here is an example:
class Rectangle<T>
where T : struct
{
}
In this example, the where restriction indicates that a vertex must be a data type that is a structure. Here is an example:
public struct NaturalPoint { public int X; public int Y; public NaturalPoint(int x = 0, int y = 0) { X = x; Y = y; } } public struct FloatingPoint { public double X; public double Y; public FloatingPoint(double x = 0d, double y = 0d) { X = x; Y = y; } } public class Rectangle<T> where T : struct { public T vertex1; public T vertex2; public T vertex3; public T vertex4; public Rectangle(T one, T two, T three, T four) { vertex1 = one; vertex2 = two; vertex3 = three; vertex4 = four; } } public class Exercise { private void Create() { NaturalPoint npt1 = new NaturalPoint(0, 2); NaturalPoint npt2 = new NaturalPoint(2, 0); NaturalPoint npt3 = new NaturalPoint(0, -2); NaturalPoint npt4 = new NaturalPoint(-2, 0); Rectangle<NaturalPoint> rect1 = new Rectangle<NaturalPoint>(npt1, npt2, npt3, npt4); FloatingPoint fpt1 = new FloatingPoint( 3, 5); FloatingPoint fpt2 = new FloatingPoint( 3, -5); FloatingPoint fpt3 = new FloatingPoint(-5, -5); FloatingPoint fpt4 = new FloatingPoint(-5, 3); Rectangle<FloatingPoint> rect2 = new Rectangle<FloatingPoint>(fpt1, fpt2, fpt3, fpt4); } }
Constraining a Parameter to a Class Type
To indicate that you want the parameter type of a generic class to be a class type, set the constraint rule to class. Here is an example:
class Rectangle<T>
where T : class
{
}
The where restriction in this case indicates that the T parameter must have been created from a class.
Constraining a Parameter to a Specific Class
Imagine you create a regular interface such as the following:
public interface IPerson { string FullName { get; set; } DateTime DateofBirth { get; set; } }
Then imagine you implement it in class. Here is an example:
public interface IPerson { string? FullName { get; set; } string? DateofBirth { get; set; } } public class PersonalIdentification : IPerson { public PersonalIdentification(string name, string dob) { DateofBirth = dob; FullName = name; } public virtual string? FullName { get; set; } public virtual string? DateofBirth { get; set; } }
When creating a generic class, you can make it implement the functionality of a certain interface or you can make sure that the class is derived from a specific base class. This would make sure that the generic class contains some useful functionality.
To create a constraint on a generic class, after the <type-name> operator, type where type-name : followed by the rule that the class must follow. For example, you may want the generic class to implement the functionality of a pre-defined class. You can create the generic class as follows:
public interface IPerson
{
string? FullName { get; set; }
string? DateofBirth { get; set; }
void Display();
}
public class Employee<T>
where T : PersonalIdentification
{
}
After creating the class, you must implement the virtual members of the where class/interface, using the rules of generic classes, the way we have done it so far.
public interface IPerson
{
string? FullName { get; set; }
string? DateofBirth { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string? FullName { get; set; }
public virtual string? DateofBirth { get; set; }
}
public class Employee<T>
where T : PersonalIdentification
{
public Employee()
{
}
public Employee(T record)
{
Identification = record;
}
public T Identification { get; set; }
}
When declaring a variable for the generic class, in its <> operator, you must enter an object of the base class. Here is an example:
using static System.Console; Title = "Personal Identification"; WriteLine("Personal Identification"); WriteLine("---------------------------------"); var std = new PersonalIdentification("James Sandt", "12/08/2002"); Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>(); empl.Identification = std; WriteLine("Full Name: {0}", empl.Identification.FullName); WriteLine($"Date Of birth: {empl.Identification.DateofBirth}"); WriteLine("================================="); public interface IPerson { string FullName { get; set; } string DateofBirth { get; set; } } public class PersonalIdentification : IPerson { public PersonalIdentification(string name, string dob) { DateofBirth = dob; FullName = name; } public virtual string? FullName { get; set; } public virtual string? DateofBirth { get; set; } } public class Employee<T> where T : PersonalIdentification { public Employee() { } public Employee(T record) { Identification = record; } public T Identification { get; set; } }
This would produce:
Personal Identification --------------------------------- Full Name: James Sandt Date Of birth: 12/08/2002 ================================= Press any key to close this window . . .
Consider the following Physician class:
public interface IPerson { string FullName { get; set; } string DateofBirth { get; set; } } public interface IProfession { string Category { get; set; } } public class Physician { public string Category { get; set; } } public class PersonalIdentification : IPerson { public PersonalIdentification(string name, string dob) { DateofBirth = dob; FullName = name; } public virtual string? FullName { get; set; } public virtual string? DateofBirth { get; set; } } public class Employee<T> where T : PersonalIdentification { public Employee() { } public Employee(T record) { Identification = record; } public T Identification { get; set; } }
Based on the restrictions, you cannot use just any class as the parameter of the generic. For example, the following would produce an error:
using static System.Console;
Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
var std = new PersonalIdentification("James Sandt", "12/08/2002");
Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
empl.Identification = std;
WriteLine(string.Format("Full Name: {0}", empl.Identification.FullName));
WriteLine(string.Format("Date Of birth: {0}", empl.Identification.DateofBirth));
var doctor = new Physician();
doctor.Category = "Health Care";
Employee<Physician> rn = new Employee<Physician>();
WriteLine("=================================");
public interface IPerson
{
string FullName { get; set; }
string DateofBirth { get; set; }
}
public interface IProfession
{
string Category { get; set; }
}
public class Physician
{
public string Category { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string? FullName { get; set; }
public virtual string? DateofBirth { get; set; }
}
public class Employee<T>
where T : PersonalIdentification
{
public Employee()
{
}
public Employee(T record)
{
Identification = record;
}
public T Identification { get; set; }
}
This would produce:
Error 1 The type 'Physician' cannot be used as type parameter 'T' in the generic type or method 'Employee<T>'. There is no implicit reference conversion from 'Physician' to 'PersonalIdentification'. C:\Temporary Projects\WhereGeneric\Exercise.cs 115 18 WhereGeneric
You can also create a constraint so that a generic class implements an interface.
Depending on the behavior you want a class to have, you may want to require that a generic class that uses a parameter must also have a default constructor. To put this restriction, you use the new keyword as a constraint. The primary formula to follow is:
class ClassName<T> where T : new()
The new factor in this formula is the new keyword. Here is an example of using it:
public interface IPerson { string FullName { get; set; } string DateofBirth { get; set; } } public class PersonalIdentification : IPerson { public PersonalIdentification(string name, string dob) { DateofBirth = dob; FullName = name; } public virtual string? FullName { get; set; } public virtual string? DateofBirth { get; set; } } public class Employee<T> where T : new() { public Employee() { } public Employee(T record) { Identification = record; } public T Identification { get; set; } }
This new operator indicates that the class (or structure) that the T parameter represents must have a default constructor. Remember that when this code executes, the Employee<> class doesn't know what T means or represents. This means that the above code will compile just fine. It is when you declare a variable of the Employee<> type that the compiler is informed about the T parameter. That's when it checks the class that T represents. If that class doesn't have a default constructor, you would receive an error. Here is an example:
using static System.Console;
Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
var std = new PersonalIdentification("James Sandt", "12/08,2002");
Employee<PersonalIdentification> empl =
new Employee<PersonalIdentification>();
empl.Identification = std;
WriteLine("=================================");
public interface IPerson
{
string FullName { get; set; }
string DateofBirth { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string? FullName { get; set; }
public virtual string? DateofBirth { get; set; }
}
public class Employee<T>
where T : new()
{
public Employee()
{
}
public Employee(T record)
{
Identification = record;
}
public T Identification { get; set; }
}
This would produce:
Error 1 'PersonalIdentification' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'Employee<T>' C:\. . .\Temporary Projects\WhereGeneric\Exercise.cs 71 18 WhereGeneric
The correction is to make sure that the class that T represents has a default constructor. Here is an example:
public interface IPerson
{
string FullName { get; set; }
string DateofBirth { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification()
{
}
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string? FullName { get; set; }
public virtual string? DateofBirth { get; set; }
}
public class Employee<T>
where T : new()
{
public Employee()
{
}
public Employee(T record)
{
Identification = record;
}
public T Identification { get; set; }
}
Constraining Various Parameters
Remember that a generic class can use more than one parameter. Here is an example:
public class Employee<T, P> { }
If you want to set a restriction of the parameters, use a where operator for each. Here is an example:
public interface IPerson
{
string FullName { get; set; }
string DateofBirth { get; set; }
}
public interface IProfession
{
string Category { get; set; }
}
public class Profession
{
public string Category { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string? FullName { get; set; }
public virtual string? DateofBirth { get; set; }
}
public class Employee<T, P>
where T : PersonalIdentification
where P : Profession
{
public Employee()
{
}
public Employee(T record, P earn)
{
Occupation = earn;
Identification = record;
}
public T Identification { get; set; }
public P Occupation { get; set; }
}
When declaring a variable for the class, provide the appropriate type. Here is an example:
using static System.Console;
Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
var persID = new PersonalIdentification("Jeannot Schwartz", "12/08,2002");
var pro = new Profession();
pro.Category = "Health Care";
Employee<PersonalIdentification, Profession> surgeon =
new Employee<PersonalIdentification, Profession>();
surgeon.Identification = persID;
surgeon.Occupation = pro;
WriteLine("Category: {0}", surgeon.Occupation.Category);
WriteLine("=================================");
public interface IPerson
{
string FullName { get; set; }
string DateofBirth { get; set; }
}
public interface IProfession
{
string Category { get; set; }
}
public class Profession
{
public string Category { get; set; }
}
public class PersonalIdentification : IPerson
{
public PersonalIdentification(string name, string dob)
{
DateofBirth = dob;
FullName = name;
}
public virtual string?? FullName { get; set; }
public virtual string?? DateofBirth { get; set; }
}
public class Employee<T, P>
where T : PersonalIdentification
where P : Profession
{
public Employee() { }
public Employee(T record, P earn)
{
Occupation = earn;
Identification = record;
}
public T Identification { get; set; }
public P Occupation { get; set; }
}
This would produce:
Personal Identification --------------------------------- Category: Health Care ================================= Press any key to close this window . . .
Remember that any regular class can implement an interface. In the same way, a normal generic class can implement any interface of your choice. Here is an example:
public class Employee<T> where T : IPerson { }
On such a class, you can also put a restriction that the class that T represents must have a default constructor. In this case, the new operator must be set as the last. Here is an example:
public class Employee<T>
where T : IPerson, new()
Introduction to Built-In Generic Interfaces and Classes
Overview
To assist you in creating generic classes, the .NET Framework provides a large library of generic interfaces. To assist you with various types of object, the .NET Framework provides a rich library of generic classes. The generic classes in the .NET Framework are extremely easy to use.
Enumerating a List
When using built-in classes, you will encounter some classes with a method named GetEnumerator. Normally, when you see a GetEnumerator() method, this means that its class is used to create or manage a list, such as an array. The immediate consequence is that you can use the foreach operator on the object (actually a list) of that class.
Spanning an Object
Introduction
To assist you in creating and controlling the memory area occupied by an array, the .NET Framework provides a built-in generic structure named Span
public readonly ref struct Span<T>
As is the case for practically all .NET Framework built-in classes, the Span<T> structure is really easy to use it. To start, declare a variable of type Span<T>. Pass the type of the array as the parameter type. To help you in initializaing the variable, the Span<T> structure is equipped with four constructors. The most common constructor takes an array as argument:
public Span (T[]? array);
Using this constructor, you can pass an array as argument. You can create an array directly in the parentheses of the constructor. Here is an example:
Span<double> numbers = new Span<double>( new double[]{ 12.44, 7.137, 525.38, 46.28, 2448.32, 75.496, 632.04 } );
An Item of the Array
After the above declaration, the variable is an array and you can use it as such. For example, you can access a member of the array using the square brackets. To support this operation, the Span<T> structure is equipped with a this property:
public ref T this[int index] { get; }
Here is an example:
using static System.Console; Span<double> numbers = new Span<double>( new double[]{ 12.44, 7.137, 525.38, 46.28, 2448.32, 75.496, 632.04 } ); WriteLine("Number: " + numbers[3].ToString()); WriteLine("===========================");
This would produce:
Number: 46.28 =========================== Press any key to close this window . . .
The Size of an Array
Like the Array class, the Span<T> structure is equipped with a property named Length:
public int Length { get; }
Enumerating an Array
The Span<T> structure is equipped with a GetEnumerator() method:
public Span<T>.Enumerator GetEnumerator ();
As a result, you can use a foreach operator to scan a Span<T> list. Here is an example:
using static System.Console; Span<double> numbers = new Span<double>( new double[]{ 12.44, 8304.68, 7.137, 525.38, 46.28, 2448.32, 75.496, 632.04 } ); foreach (double number in numbers) { WriteLine("Number: " + number.ToString()); } WriteLine("===========================");
This would produce:
Number: 12.44 Number: 7.137 Number: 525.38 Number: 46.28 Number: 2448.32 Number: 75.496 Number: 632.04 =========================== Press any key to close this window . . .
Copying an Array
If you have an existing array and want to control how it is stored in memory, you can pass that array to a Span<T> variable. To perform this operation, you have two options. As one solution, you can pass the array variable to the above Span<T> constructor. This can be done as follows:
using static System.Console; double[] values = { 12.44, 8304.68, 7.137, 525.38, 46.28, 2448.32, 75.496, 632.04 }; Span<double> numbers = new Span<double>(values); foreach (double number in numbers) { WriteLine("Number: " + number.ToString()); } WriteLine("===========================");
Another solution is to directly assign an array to a Span<T> variable. This can be done as follows:
using static System.Console; double[] values = { 12.44, 8304.68, 7.137, 525.38, 46.28, 2448.32, 75.496, 632.04 }; Span<double> numbers = values; foreach (double number in numbers) { WriteLine("Number: " + number.ToString()); } WriteLine("===========================");
Slicing an Array
Slicing a list consists of creating a sub-list using a range of items from an existing list. To support this operation, the Span<T> structure is equipped with an overloaded method named Slice. One of the syntaxes of this method takes one integer argument:
public Span<T> Slice (int start);
This version specifies the index from which to start considering the members of the existing array. Another version takes two arguments:
public Span<T> Slice (int start, int length);
The first argument specifies from which index to start considering the items of the list. The second argument is the number of items from the starting index. Here is an example:
using static System.Console;
double[] values = { 12.44, 8304.68, 7.137, 525.38,
46.28, 2448.32, 75.496, 632.04 };
Span<double> numbers = values;
foreach (double number in numbers)
{
WriteLine("Number: " + number.ToString());
}
WriteLine("--------------------------");
Span<double> part = numbers.Slice(2, 4);
foreach (double number in part)
{
WriteLine("Number: " + number.ToString());
}
WriteLine("===========================");
This would produce:
Number: 12.44 Number: 8304.68 Number: 7.137 Number: 525.38 Number: 46.28 Number: 2448.32 Number: 75.496 Number: 632.04 -------------------------- Number: 7.137 Number: 525.38 Number: 46.28 Number: 2448.32 =========================== Press any key to close this window . . .
|
|||
Previous | Copyright © 2008-2025, FunctionX | Tuesday 19 October 2021 | Next |
|