Руководство C# Generics

1- Класс Generics

Пример ниже дает определение классу generics. KeyValue это класс generics, который содержит пару ключей и значение (key/value).
 
KeyValue.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class KeyValue<K, V>
   {

       private K key;
       private V value;

       public KeyValue(K key, V value)
       {
           this.key = key;
           this.value = value;
       }

       public K GetKey()
       {
           return key;
       }

       public void SetKey(K key)
       {
           this.key = key;
       }

       public V GetValue()
       {
           return value;
       }

       public void SetValue(V value)
       {
           this.value = value;
       }

   }

}
K, V в классе  KeyValue<K,V> называются параметрами  generics, это определенный вид данных. При использовании этого класса, вам нужно опредеилть конкретный параметр.
 
Посмотрим пример использования класса KeyValue.
KeyValueDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    public class KeyValueDemo
    {

        public static void Main(string[] args)
        {
            // Создать объект KeyValue.
            // int: Номер телефона  (K = int)
            // string: Имя пользователя.  (V = string).
            KeyValue<int, string> entry = new KeyValue<int, string>(12000111, "Tom");

            // C# понимает возвращенный вид как int (K = int).
            int phone = entry.GetKey();

            // C# понимает возвращенный вид как string (V = string).
            string name = entry.GetValue();

            Console.WriteLine("Phone = " + phone + " / name = " + name);

            Console.Read();
        }

    }

}
Запуск примера:

2- Наследование класса Generics

Расширенный класс из класса generics, который может определить вид параметра для generics, оставить параметры generics или добавить параметры generics.

Пример 1:

PhoneNameEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

    // Данный класс расширен из класса KeyValue<K,V>
    // И ясно определяет вид для 2 параметров K & V
    // K = int  (Номер телефона).
    // V = string   (Имя пользователя).
    public class PhoneNameEntry : KeyValue<int, string>
    {

        public PhoneNameEntry(int key, string value)
            : base(key, value)
        {

        }

    }

}
Пример использования  PhoneNameEntry:
PhoneNameEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    public class PhoneNameEntryDemo
    {

        public static void Main(string[] args)
        {

            PhoneNameEntry entry = new PhoneNameEntry(12000111, "Tom");

            // C# понимает возвращенный вид как int.
            int phone = entry.GetKey();

            // C# понимает возвращенный вид как string.
            string name = entry.GetValue();

            Console.WriteLine("Phone = " + phone + " / name = " + name);

            Console.Read();

        }

    }

}

Example 2:

StringAndValueEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

    // Данный класс расширен из класса KeyValue<K,V>
    // Ясно определить параметр K как String.
    // Оставить вид параметра generic V.
    public class StringAndValueEntry<V> : KeyValue<string, V>
    {

        public StringAndValueEntry(string key, V value)
            : base(key, value)
        {
        }

    }

}
Пример использования класса  StringAndValueEntry:
StringAndValueEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    public class StringAndValueEntryDemo
    {

        public static void main(String[] args)
        {

            // (Код сотрудника, Имя сотрудника).
            // V = string (Имя сотрудника)
            StringAndValueEntry<String> entry = new StringAndValueEntry<String>("E001", "Tom");

            String empNumber = entry.GetKey();

            String empName = entry.GetValue();

            Console.WriteLine("Emp Number = " + empNumber);
            Console.WriteLine("Emp Name = " + empName);

            Console.Read();

        }
    }

}

Пример 3:

KeyValueInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

    // Данный класс расширен из класса KeyValue<K,V>.
    // Он имеет еще один параметр Generics I.
    public class KeyValueInfo<K, V, I> : KeyValue<K, V>
    {

        private I info;

        public KeyValueInfo(K key, V value)
            : base(key, value)
        {
        }

        public KeyValueInfo(K key, V value, I info)
            : base(key, value)
        {
            this.info = info;
        }

        public I GetInfo()
        {
            return info;
        }

        public void GetInfo(I info)
        {
            this.info = info;
        }

    }

}

3- Generics Interface

Interface с параметром Generics:
GenericInterface.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
  public interface GenericInterface<G>
  {

      G DoSomething();

  }

}
Например, класс выполняет (implements) Interface:
GenericInterfaceImpl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   public class GenericInterfaceImpl<G> : GenericInterface<G>
   {

       private G something;

       public G DoSomething()
       {
           return something;
       }

   }

}

4- Использование Generic с Exception

Вы можете определить Exception с параметрами Generics.
MyException.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class MyException<E> : ApplicationException
   {

   }

}
Использование действительного  Generic Exception:
UsingGenericExceptionValid01.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    class UsingGenericExceptionValid01
    {

        public void SomeMethod()
        {
            try
            {
                // ...
            }
            // Действительно
            catch (MyException<string> e)
            {
                // Сделать что-то здесь.
            }
            // Действительно
            catch (MyException<int> e)
            {
                // Сделать что-то здесь.         
            }
            catch (Exception e)
            {
            }
        }

    }

}
Использование действительного  Generic Exception:
UsingGenericExceptionValid02.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    class UsingGenericExceptionValid02<K>
    {
        public void SomeMethod()
        {
            try
            {
                // ...
            }
            // Действительно
            catch (MyException<string> e)
            {
                // Сделать что-то здесь.
            }
            // Действительно
            catch (MyException<K> e)
            {
                // Сделать что-то здесь.
            }
            catch (Exception e)
            {
            }
        }
    }

}
Использование недействительного  Generic Exception:
UsingGenericExceptionInvalid.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    class UsingGenericExceptionInvalid
    {
        public void SomeMethod()
        {
            try
            {
                // ...
            }
            // Действительно
            // ​​​​​​​
            catch (MyException<string> e)
            {
                // Сделать что-то здесь.
            } 
            // Invalid (Unknown parameter K) *********** 
            // catch (MyException<K> e)
            // {
                // ...            
            // }
            catch (Exception e)
            {
            }
        }
    }

}

5- Метод generics

Метод в класс или интерфейсе может быть стать Generic (generify).
MyUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    public class MyUtils
    {
        // <K,V> : Говорит этот метод имеет 2 вида параметра K,V
        // Метод возвращает вид K.
        public static K GetKey<K, V>(KeyValue<K, V> entry)
        {
            K key = entry.GetKey();
            return key;
        }

        // <K,V> : Говорит этот метод имеет 2 вида параметра K,V
        // Метод возвращает вид V.
        public static V GetValue<K, V>(KeyValue<K, V> entry)
        {
            V value = entry.GetValue();
            return value;
        }

        // List<E>: Список содержит элементы вида E
        // Данный метод возвращает вид E.
        public static E GetFirstElement<E>(List<E> list, E defaultValue)
        {
            if (list == null || list.Count == 0)
            {
                return defaultValue;
            }
            E first = list.ElementAt(0);
            return first;
        }

    }

}
Пример использования метода generics:
MyUtilsDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    public class MyUtilsDemo
    {

        public static void Main(string[] args)
        {

            // K = int: Phone
            // V = string: Name
            KeyValue<int, string> entry1 = new KeyValue<int, String>(12000111, "Tom");
            KeyValue<int, string> entry2 = new KeyValue<int, String>(12000112, "Jerry");

            // (K = int).
            int phone = MyUtils.GetKey(entry1);
            Console.WriteLine("Phone = " + phone);

            // Списко содержит элементы вида KeyValue<int,string>.
            List<KeyValue<int, string>> list = new List<KeyValue<int, string>>();

            // Добавить элемент в список.
            list.Add(entry1);
            list.Add(entry2);

            KeyValue<int, string> firstEntry = MyUtils.GetFirstElement(list, null);

            if (firstEntry != null)
            {
                Console.WriteLine("Value = " + firstEntry.GetValue());
            }

            Console.Read();
        }

    }

}
Запуск примера:

6- Инициализация объекта Generic

Иногда вы хотите инициализировать объект  Generic:
public void DoSomething<T>()
{

     // Инициализировать объект Generic
     T t = new T(); // Error

}
Причина ошибки выше, является то, что параметр Т возможно не имеет конструктор (constructor) T(), поэтому ам нужно добавить принуждение when T : new(). Посмотрим пример ниже:
GenericInitializationExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
    class GenericInitializationExample 
    {
        // Вид T должен быть видом, имеющим Constructor по умолчанию.
        public void DoSomeThing<T>()
            where T : new()
        {
            T t = new T();
        }

        // Вид T должен быть видом, имеющим Constructor по умолчанию.
        // и расширенным из класса KeyValue.
        public void ToDoSomeThing<K>()
            where K: KeyValue<K,string>, new( )
        {
            K key = new K();
        }


        public T DoDefault<T>()
        {
            // Возвращает null если T является ссылочным видом (reference type).
            // Или 0 если T является видом числа  (int, float,..)
            return default(T);
        }
    }

}

7- Массив Generic

В C# вы можете объявить массив  Generics:
// Инициализировать массив.
T[] myArray = new T[10];
GenericArrayExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class GenericArrayExample
   {

       public static T[] FilledArray<T>(T value, int count)
       {
           T[] ret = new T[count];
           for (int i = 0; i < count; i++)
           {
               ret[i] = value;
           }
           return ret;
       }

       public static void Main(string[] args)
       {

           string value = "Hello";

           string[] filledArray = FilledArray<string>(value, 10);

           foreach (string s in filledArray)
           {
               Console.WriteLine(s);
           }
       }
   }

}