Basic part of C - generics

preface

In the development of programming, we often encounter functional modules with very similar functions, but the data they process is different, so we will use multiple methods to deal with different data types. But at this time, we will wonder if there is any way to use the same method to pass different types of parameters?

At this time, generics are born by operation to solve this problem.

Generics is a new syntax introduced in C × 2.0, which is provided by framework upgrade.

explain

Generics operate on multiple data types in the same code by parameterizing types. For example, use the generic type parameter T to define a class Stack,

It can be instantiated with Stack, Stack, or Stack so that the class Stack can handle int, string, Person type data. This avoids the cost and risk of runtime type conversion or boxing operations. Generics remind you to blur things.

Using generic types at the same time maximizes code reuse, type security, and performance.

You can create: generic interfaces, generic classes, generic methods, generic events, and generic delegates

start

Generic class

Generic classes encapsulate operations that are not specific to a specific data type. The most common use of generic classes is for collections such as linked lists, hash tables, stacks, queues, and trees. Regardless of the type of data stored, operations such as adding items and removing items from a collection are performed in much the same way.

    static void Main(string[] args)
    {

        // T is of type int
        GenericClass<int> genericInt = new GenericClass<int>();
        genericInt._T = 123;
        // T is of type string
        GenericClass<string> genericString = new GenericClass<string>();
        genericString._T = "123";

    }

Create a new GenericClass class

    /// <summary>
    ///Generic class
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericClass<T>
    {
        public T _T;
    }

generic method

Generic method is a method declared by type parameter, which can satisfy different parameter types with one method

    static void Main(string[] args)
    {
        #region generic method
        Console.WriteLine("************Generic**************");
        int iValue = 123;
        string sValue = "456";
        DateTime dtValue = DateTime.Now;
        object oValue = "MrValue";
        GenericMethod.Show<int>(iValue);//Type parameter needs to be specified
        //GenericMethod.Show < string > (Ivalue); / / must match
        GenericMethod.Show(iValue);//Can be omitted and calculated automatically
        GenericMethod.Show<string>(sValue);
        GenericMethod.Show<DateTime>(dtValue);
        GenericMethod.Show<object>(oValue);
        #endregion

    }

Create a new generic method

/// <summary>
///Generic methods
/// </summary>
public class GenericMethod
{
    /// <summary>
    ///2.0 new syntax
    ///The generic method is to use one method to satisfy different parameter types; to do the same thing
    ///The parameter type is not written. It is specified when calling
    ///Delay declaration: postpone the declaration of parameter type to the call
    ///Put off everything that can be put off
    ///It's not a syntax sugar, it's a 2.0 framework upgrade
    ///Compiler support + JIT support required
    /// </summary>
    ///< typeparam name = "t" > t / s do not use keywords or conflict with other types < / typeparam >
    /// <param name="tParameter"></param>
    public static void Show<T>(T tParameter)
    {
        Console.WriteLine("This is {0},parameter={1},type={2}",
            typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
    }
}

generic interface

It is often useful to define interfaces for generic collection classes or generic classes that represent items in a collection. In c, type parameters are enclosed by angle brackets "< >", indicating generics. When declaring a generic interface, the only difference from declaring a generic interface is the addition of one. In general, declaring a generic interface follows the same rules as declaring a non generic interface.

After the generic interface is defined, the subclasses of this interface are defined. There are two ways to define subclasses of generic interfaces.

(1) Declare generics directly after subclasses.

(2) The generic type is explicitly given in the interface implemented by the subclass.

    static void Main(string[] args)
    {
        #region generic interface
        CommonInterface commonInterface = new CommonInterface();
        commonInterface.GetT("123");
        #endregion
    }

newly build GenericInterface.cs Class file

        /// <summary>
        ///Generic class
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class GenericClass<T>
        {
            public T _T;
        }

        /// <summary>
        ///Generic interface
        /// </summary>
        public interface IGenericInterface<T>
        {
            //Return value of generic type
            T GetT(T t);
        }


        /// <summary>
        ///You must specify a specific type when using generics,
        ///The concrete type here is int
        /// </summary>
        public class CommonClass : GenericClass<int>
        {

        }

        /// <summary>
        ///Specific type must be specified
        /// </summary>
        public class CommonInterface : IGenericInterface<string>
        {
            public string GetT(string t)
            {
                return t;
            }
        }

        /// <summary>
        ///Subclasses are also generic. You can inherit without specifying specific types
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CommonClassChild<T> : GenericClass<T>
        {

        }

generic delegate

Generic delegates mainly want to talk about Action and Func, which are often seen in Linq.

Action can only delegate methods that must have no return value

Fun is just that a delegate must have a method that returns a value

No matter whether it's a generic delegate or not, as long as it's a delegate, it can use Lamdba expressions, because both Lamdba expressions and anonymous functions are actually function variables.

The following simple demo says the following two uses. This meeting is half of the basic linq meeting.

    static void Main(string[] args)
    {
        #region generic delegation
        Action<string> action = s => {
            Console.WriteLine(s);
        };
        action("i3yuan");
        Func<int, int, int> func = (int a, int b) => {
            return a + b;
        };
        Console.WriteLine("sum:{0}", func(1,1));
        Console.ReadLine();
        #endregion
    }

In fact, the above are functions as variables, which is also the idea of delegation. action is to instantiate a function variable with only one string parameter and no return value. func is an instantiation of a function variable with two int type parameters and an int return value.

It can be seen that through the combination of Lamdba expression and generics, it is more convenient and practical for developers.

Another way to introduce delegation

Whether within or outside the class definition, delegates can define their own type parameters. The code that references a generic delegate can specify type parameters to create a closed construction type, just like instantiating a generic class or calling a generic method, as shown in the following example:

public delegate void MyDelegate<T>(T item);
public void Notify(int i){}
//...
 
MyDelegate<int> m = new MyDelegate<int>(Notify);
 
C#A new feature in version 2.0 is called method group conversion, which can be used for both specific agent and generic agent types. Using method group conversion, you can write the above line as a simplified syntax:
MyDelegate<int> m = Notify;
 
//A delegate defined in a generic class can use the type parameters of the generic class as the methods of the class.
class Stack<T>
{
T[] items;
      int index
//...
public delegate void StackDelegate(T[] items);
}
 
//The code referencing the delegate must specify the type parameter of the class, as follows:
 
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate myDelegate = StackNotify;
 
 
//Generic delegates are particularly useful when defining events based on typical design patterns. Because sender[JX2], there is no need to convert with Object.
public void StackEventHandler<T,U>(T sender, U eventArgs);
class Stack<T>
{
    //...
    public class StackEventArgs : EventArgs{...}
    public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
    protected virtual void OnStackChanged(StackEventArgs a)
    {
      stackEvent(this, a);
    }
}
class MyClass
{
  public static void HandleStackChange<T>(Stack<T> stack, StackEventArgs args){...};
}
Stack<double> s = new Stack<double>();
MyClass mc = new MyClass();
s.StackEventHandler += mc.HandleStackChange;


Generic constraints

The so-called generic constraint is actually the constraint type T. So t must follow certain rules. For example, t must inherit from a class, or t must implement an interface, and so on. So how to assign constraints to generics? In fact, it's also very simple. You only need where keyword and constraint conditions.

Define a People class with properties and methods:

    public interface ISports
    {
        void Pingpang();
    }
    public interface IWork
    {
        void Work();
    }
    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public void Hi()
        {
            Console.WriteLine("Hi");
        }
    
    }
    public class Chinese : People, ISports, IWork
    {
        public void Tradition()
        {
            Console.WriteLine("Benevolence, righteousness, propriety, wisdom and trust, gentleness, courtesy and thrift");
        }
        public void SayHi()
        {
            Console.WriteLine("Have you eaten yet?");
        }
    
        public void Pingpang()
        {
            Console.WriteLine("play table tennis...");
        }
    
        public void Work()
        {
            throw new NotImplementedException();
        }
    } 
    public class Hubei : Chinese
    {
        public Hubei(int version)
        { }
    
        public string Changjiang { get; set; }
        public void Majiang()
        {
            Console.WriteLine("Playing mahjong..");
        }
    }
    public class Japanese : ISports
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public void Hi()
        {
            Console.WriteLine("Hi");
        }
        public void Pingpang()
        {
            Console.WriteLine("play table tennis...");
        }
    }

Printing method

    /// <summary>
    ///Print object values
    ///1 the object type is the parent of all types
    ///By inheritance, a subclass has all the properties and behaviors of the parent; any place where the parent appears can be replaced by a subclass
    ///Adding the object reference type to the passed value type int will result in a performance loss of unboxing
    ///Unsafe type
    /// </summary>
    /// <param name="oParameter"></param>
    public static void ShowObject(object oParameter)
    {
        Console.WriteLine("This is {0},parameter={1},type={2}",
            typeof(Constraint), oParameter.GetType().Name, oParameter);

        Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}");

    }

In the main method

    static void Main(string[] args)
    {
        #Region constraint interface constraint
        Console.WriteLine("************Constraint*****************");
        {
            People people = new People()
            {
                Id = 123,
                Name = "Go your own way"
            };
            Chinese chinese = new Chinese()
            {
                Id = 234,
                Name = "a sunny day"
            };
            Hubei hubei = new Hubei(123)
            {
                Id = 345,
                Name = "Fleeting years"
            };
            Japanese japanese = new Japanese()
            {
                Id = 7654,
                Name = "i3yuan"//
            };
            CommonMethod.ShowObject(people);
            CommonMethod.ShowObject(chinese);
            CommonMethod.ShowObject(hubei);
            CommonMethod.ShowObject(japanese);
  
            Console.ReadLine();
        }
        #endregion
    }

There are a total of five generic constraints.

constraint explain
T: Structure Type parameter must be of value type
T: Class Type parameters must be reference types; this also applies to any class, interface, delegate, or array type.
T: new() Type parameters must have a public constructor with no parameters. When used with other constraints, the new() constraint must be specified last.
T: < base class name > Type parameters must be or derived from the specified base class.
T: < interface name > The type parameter must be the specified interface or implement the specified interface. You can specify multiple interface constraints. Constraint interfaces can also be generic.

1. Base class constraint

The method constraint T type printed above must be of type People.

///
///Base class constraint: constraint T must be of type People or subclass of People
///You can use all the attribute methods of the base class -- rights
///2. Compulsory guarantee T must be a subclass of People or People --- obligation

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter) where T : People
        {
            Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
            tParameter.Hi();
        }

be careful:

When the base class is constrained, the base class cannot be a sealed class, that is, it cannot be a sealed class. The sealed class indicates that the class cannot be inherited, so it is meaningless to use it as a constraint here, because the sealed class has no subclass.

2. Interface constraints

        /// <summary>
        ///Interface constraints
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : ISports
        {
            t.Pingpang();
            return t;
        }

3. Reference type constraint class

The reference type constraint guarantees that T must be of reference type.

        ///Reference type constraint
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : class
        {
            return t;
        }

4. Value type constraint struct

The value type constraint guarantees that T must be of value type.

        ///Value type type constraint
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : struct
        {
            return t;
        }

5. Parameterless constructor constraint new()

        ///new() constraint
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : new()
        {
            return t;
        }

Generic constraints can also constrain multiple at the same time, for example:

        /// <summary>
        ///Generics: different parameter types can come in; any type can come in. Do you know who I am?
        ///Without restraint, there will be no freedom
        ///Generic Constraint -- base class constraint (cannot be sealed):
        ///You can use all the attribute methods of the base class -- rights
        ///2. Compulsory guarantee T must be a subclass of People or People --- obligation
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
        where T : People, ISports, IWork, new()
        {
            Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
            tParameter.Hi();
            tParameter.Pingpang();
            tParameter.Work();
        }

Note: when there are multiple generic constraints, the new() constraint must be at the end.

Covariance and inversion of generics

    public class Animal
    {
        public int Id { get; set; }
    }

    public class Cat : Animal
    {
        public string Name { get; set; }
    }
    

 static void Main(string[] args)
 {
    #region covariance and inversion

    // Declare the Animal class directly
    Animal animal = new Animal();
    // Declare Cat classes directly
    Cat cat = new Cat();
    // Declare that the subclass object points to the parent class
    Animal animal2 = new Cat();
    // Declare a collection of Animal classes
    List<Animal> listAnimal = new List<Animal>();
    // Declare a collection of Cat classes
    List<Cat> listCat = new List<Cat>();

    #endregion
 }

So the question is: is the following code correct?

1 List<Animal> list = new List<Cat>();

Some people may think it's right: because a Cat belongs to Animal, then a group of Cat should also belong to Animal. But in fact, it's wrong to declare it like this: there is no parent-child relationship between List and List.

Then we can use covariance and inversion.

1 // covariant
2 IEnumerable<Animal> List1 = new List<Animal>();
3 IEnumerable<Animal> List2 = new List<Cat>();

F12 view definition:

As you can see, there is an out keyword before T in the generic interface, and T can only be a return value type, not a parameter type, which is covariant. After covariance is used, the base class is declared on the left, and the base class or subclass of the base class can be declared on the right.

Covariance can be used not only for interfaces, but also for delegates:

 Func<Animal> func = new Func<Cat>(() => null);

In addition to using the. NET framework to define the concept, we can also customize covariance, for example:

    /// <summary>
    ///out covariance can only be a return result
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListOut<out T>
    {
        T Get();
    }

    public class CustomerListOut<T> : ICustomerListOut<T>
    {
        public T Get()
        {
            return default(T);
        }
    }

Use custom covariance:

 // Use custom covariance
 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
 ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();

Let's see the inverter.

There is an In keyword before the T of the generic interface, and t can only be a method parameter, not a return value type, which is the inversion. See the following custom inverter:

    /// <summary>
    ///Inversion can only be method parameter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListIn<in T>
    {
        void Show(T t);
    }

    public class CustomerListIn<T> : ICustomerListIn<T>
    {
        public void Show(T t)
        {
        }
    }

Use custom inverter:

 // Use custom inverter
 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
 ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();

Covariance and inversion can also be used at the same time. See the following example:

    /// <summary>
    ///inT inverter
    ///outT covariance
    /// </summary>
    /// <typeparam name="inT"></typeparam>
    /// <typeparam name="outT"></typeparam>
    public interface IMyList<in inT, out outT>
    {
        void Show(inT t);
        outT Get();
        outT Do(inT t);
    }

    public class MyList<T1, T2> : IMyList<T1, T2>
    {

        public void Show(T1 t)
        {
            Console.WriteLine(t.GetType().Name);
        }

        public T2 Get()
        {
            Console.WriteLine(typeof(T2).Name);
            return default(T2);
        }

        public T2 Do(T1 t)
        {
            Console.WriteLine(t.GetType().Name);
            Console.WriteLine(typeof(T2).Name);
            return default(T2);
        }
    }

use:

 IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
 IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//covariant
 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//Inverter
 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//Contravariant + covariant

Notes on variability

  • Changes only apply to reference types because you cannot derive other types directly from value types
  • Show changes use the in and out keywords only for delegates and interfaces, not for classes, structures, and methods
  • Delegate and interface type parameters that do not include in and out keywords are called invariant

Generic caching

As we learned before, no matter how many times you instantiate a static type in a class, there will only be one in memory. A static constructor is only executed once. In a generic class, the T type is different. Each different T type will produce a different copy, so it will produce different static properties and different static constructors. See the following example:

public class GenericCache<T>
{
    static GenericCache()
    {
        Console.WriteLine("This is GenericCache static constructor ");
        _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
    }

    private static string _TypeTime = "";

    public static string GetCache()
    {
        return _TypeTime;
    }
}
public class GenericCacheTest
{
    public static void Show()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(GenericCache<int>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<long>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<DateTime>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<string>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
            Thread.Sleep(10);
        }
    }
}

The Main() method calls:

 static void Main(string[] args)
 {
    #region generic cache
	GenericCacheTest.Show();
    #endregion
 }

result:

As you can see from the screenshot above, generics will create a copy of each type, so the static constructor will execute five times. And the value of each static property is the same. With this feature of generics, caching can be implemented.

Note: you can cache only once for different types. Generic caching is more efficient than dictionary caching. Generic cache cannot be actively released.

be careful

1. default keyword in generic code

One of the problems in generic classes and generic methods is how to assign default values to parameterized types. At this time, the following two points cannot be known in advance:

  • Will T be a value type or a reference type

  • If T is a value type, will T be a value or a structure

For a variable t of parameterized type T, t = null statement is legal only when t is a reference type; t = 0 is only valid for numerical value, but not for structure. The solution to this problem is to use the default keyword, which returns null for the reference type and zero for the numeric type of the value type. For a structure, it returns each member of the structure, and returns zero or null depending on whether the member is a value type or a reference type. The following example of the GenericList class shows how to use the default keyword.

    static void Main(string[] args)
    {
        #region generic code default keyword default
        // Test with a non empty list of integers
        GenericList<int> gll = new GenericList<int>();
        gll.AddNode(5);
        gll.AddNode(4);
        gll.AddNode(3);
        int intVal = gll.GetLast();
        // The next line shows 5
        Console.WriteLine(intVal);

        // Test with an empty list of integers
        GenericList<int> gll2 = new GenericList<int>();
        intVal = gll2.GetLast();
        // The next line shows 0
        Console.WriteLine(intVal);

        // Test with a list of non empty strings
        GenericList<string> gll3 = new GenericList<string>();
        gll3.AddNode("five");
        gll3.AddNode("four");
        string sVal = gll3.GetLast();
        // The next line shows five
        Console.WriteLine(sVal);

        // Test with an empty string list
        GenericList<string> gll4 = new GenericList<string>();
        sVal = gll4.GetLast();
        // The next line shows a blank line
        Console.WriteLine(sVal);
        #endregion
        Console.ReadKey();
    }
    public class GenericList<T>
    {
        private class Node
        {
            // Each node has a reference to the next node in the list
            public Node Next;
            // Each node has a value of type T
            public T Data;
        }

        // The list is initially empty
        private Node head = null;

        // Add a node at the beginning of the list with t as its data value
        public void AddNode(T t)
        {
            Node newNode = new Node();
            newNode.Next = head;
            newNode.Data = t;
            head = newNode;
        }

        // The following method returns a list of data values stored in the last node. If the list is empty, it returns the default value of type T
        public T GetLast()
        {
            // The value of the temporary variable is returned as the value of the method 
            // The following statement initializes the temporary temperature 
            // The default value of type T. If the list is empty, the default value will be returned
            T temp = default(T);

            Node current = head;
            while (current != null)
            {
                temp = current.Data;
                current = current.Next;
            }
            return temp;
        }
    }

2. Generic set

In general, it is recommended that you use generic collections, because this gives you the immediate benefit of type security without having to derive from the base collection type and implement type specific members. The following generic types correspond to existing collection types:

1,List Is corresponding to ArrayList The generic class of.
2,Dictionary Is corresponding to Hashtable The generic class of.
3,Collection Is corresponding to CollectionBase The generic class of.
4,ReadOnlyCollection Is corresponding to ReadOnlyCollectionBase The generic class of.
5,Queue,Stack and SortedList A generic class corresponds to a non generic class with the same name.
6,LinkedList Is a general-purpose list of links, which provides insert and remove operations with O(1) computational complexity.
7,SortedDictionary It is a sort dictionary, whose operation complexity of insertion and retrieval is O(log n), which makes it a very useful alternative type of SortedList.
8,KeyedCollection Is a hybrid type between a list and a dictionary that provides a way to store objects that contain their own keys.

summary

  1. As a developer, when our program code has the same logic, it may be methods, interfaces, classes or delegates, but some parameter types are different, we hope that the code can be general, reused, or even lazy, or in the case of uncertain types, we should consider using generic thinking to achieve.
  2. In non generic programming, although all things can be passed as objects, type conversion is inevitable in the process of passing. Type conversion is not safe at run time. Using generic programming will reduce unnecessary type conversion and improve security. Not only the value type, but also the reference type, so it is necessary to use the generic collection as much as possible.
  3. In non generic programming, when simple types are passed as objects, the operations of boxing and unboxing will occur, which are both expensive. With generic programming, you don't have to box or unpack.

reference resources file C diagram course

Note: search for official account [DotNet Technology Valley] - reply [C# diagram], available C diagram course file

Keywords: C# Programming Attribute

Added by Xasho on Sun, 31 May 2020 15:26:17 +0300