Обобщенные интерфейсы

71

Помимо обобщенных классов и методов, в C# допускаются обобщенные интерфейсы. Такие интерфейсы указываются аналогично обобщенным классам. Применяя обобщения, можно определять интерфейсы, объявляющие методы с обобщенными параметрами. Давайте рассмотрим пример применения обобщенных интерфейсов:

using System;

namespace ConsoleApplication1
{
    // Объявляем обобщенный интерфейс
    public interface ISort<T> 
        where T : struct
    {
        void ReWrite();
    }

    // Реализуем интерфейс в классе MyObj
    class MyObj<T> : ISort<T> where T : struct
    {
        public int longOb { get; set; }
        T[] myarr;

        public MyObj(int i)
        {
            longOb = i;
        }

        public MyObj(int i, T[] arr)
        {
            longOb = i;
            myarr = new T[i];
            for (int j = 0; j < arr.Length; j++)
                myarr[j] = arr[j];
        }

        public void ReWrite()
        {
            Console.WriteLine("Тип: {0}",typeof(T));
            Console.WriteLine("Массив объектов: ");
            foreach (T t in myarr)
                Console.Write("{0}\t",t);
            Console.WriteLine("\n");
        }
    }

    class Program
    {
        static void Main()
        {
            byte[] MyArrByte = new byte[5] {4, 5, 18, 56, 8};
            MyObj<byte> ByteConst = new MyObj<byte>(MyArrByte.Length,MyArrByte);
            ByteConst.ReWrite();

            float[] MyArrFloat = new float[8] { 12.0f, 1f, 3.4f, 2.8f, -334.7f, -2f, 7.89f, 0 };
            MyObj<float> FloatConst = new MyObj<float>(MyArrFloat.Length, MyArrFloat);
            FloatConst.ReWrite();

            Console.ReadLine();
        }
    }
}
Реализация обобщенных интерфейсов

Как видите в данном примере объявляется обобщенный интерфейс ISort в котором используется метод ReWrite(). Класс MyObj реализует данный интерфейс, при этом данный класс должен иметь такое же обобщение как и интерфейс который он реализует. Обратите внимание, что обобщенный интерфейс ISort использует ограничение на тип значения, поэтому данное ограничение необходимо указать и при объявлении класса MyObj. В методе Main() создается несколько разнотипных экземпляров данного класса, и используется реализованный метод ReWrite().

Сравнение экземпляров параметра типа

Иногда возникает потребность сравнить два экземпляра параметра типа. Допустим, что требуется написать обобщенный метод IsIn(), возвращающий логическое значение true, если в массиве содержится некоторое значение. Для этой цели сначала можно попробовать сделать следующее:

// Не годится!
public static bool IsIn<T>(T what, T[] obs) {
  foreach(T v in obs)
    if(v == what) // Ошибка!
      return true;
      
  return false;
}

К сожалению, эта попытка не пройдет. Ведь параметр T относится к обобщенному типу, и поэтому компилятору не удастся выяснить, как сравнивать два объекта. Требуется ли для этого поразрядное сравнение или же только сравнение отдельных полей? А возможно, сравнение ссылок? Вряд ли компилятор сможет найти ответы на эти вопросы. Правда, из этого положения все же имеется выход.

Для сравнения двух объектов параметра обобщенного типа они должны реализовывать интерфейс IComparable или IComparable<T> и/или интерфейс IEquatable<T>. В обоих вариантах интерфейса IComparable для этой цели определен метод CompareTo(), а в интерфейсе IEquatable<T> — метод Equals(). Разновидности интерфейса IComparable предназначены для применения в тех случаях, когда требуется определить относительный порядок следования двух объектов. А интерфейс IEquatable служит для определения равенства двух объектов. Все эти интерфейсы определены в пространстве имен System и реализованы во встроенных в C# типах данных, включая int, string и double. Но их нетрудно реализовать и для собственных создаваемых классов. Итак, начнем с обобщенного интерфейса IEquatable<T>.

Интерфейс IEquatable<T> объявляется следующим образом:

public interface IEquatable<T>

Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом интерфейсе определяется метод Equals(), как показано ниже:

bool Equals(Т other)

В этом методе сравниваются вызывающий объект и другой объект, определяемый параметром other. В итоге возвращается логическое значение true, если оба объекта равны, а иначе — логическое значение false.

В ходе реализации интерфейса IEquatable<T> обычно требуется также переопределять методы GetHashCode() и Equals(Object), определенные в классе Object, чтобы они оказались совместимыми с конкретной реализацией метода Equals(). Ниже приведен пример программы, в которой демонстрируется исправленный вариант упоминавшегося ранее метода IsIn():

// Требуется обобщенный интерфейс IEquatable<T>.
public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T> {
  foreach(T v in obs)
    if(v.Equals(what)) // Применяется метод Equals().
      return true;
  return false;
}

Обратите внимание в приведенном выше примере на применение следующего ограничения:

where T : IEquatable<T>

Это ограничение гарантирует, что только те типы, в которых реализован интерфейс IEquatable, являются действительными аргументами типа для метода IsIn(). Внутри этого метода применяется метод Equals (), который определяет равенство одного объекта другому.

Для определения относительного порядка следования двух элементов применяется интерфейс IComparable. У этого интерфейса имеются две формы: обобщенная и необобщенная. Обобщенная форма данного интерфейса обладает преимуществом обеспечения типовой безопасности, и поэтому мы рассмотрим здесь именно ее. Обобщенный интерфейс IComparable<T> объявляется следующим образом:

public interface IComparable<T>

Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом интерфейсе определяется метод CompareTo(), как показано ниже:

int CompareTo(Т other)

В этом методе сравниваются вызывающий объект и другой объект, определяемый параметром other. В итоге возвращается 1, если вызывающий объект оказывается больше, чем объект other; и отрицательное значение, если вызывающий объект оказывается меньше, чем объект other.

Для того чтобы воспользоваться методом CompareTo(), необходимо указать ограничение, которое требуется наложить на аргумент типа для реализации обобщенного интерфейса IComparable<T>. А затем достаточно вызвать метод CompareTo(), чтобы сравнить два экземпляра параметра типа.

Ниже приведен пример применения обобщенного интерфейса IComparable<T>. В этом примере вызывается метод InRange(), возвращающий логическое значение true, если объект оказывается среди элементов отсортированного массива:

// Требуется обобщенный интерфейс IComparable<T>. В данном методе
// предполагается, что массив отсортирован. Он возвращает логическое
// значение true, если значение параметра what оказывается среди элементов
// массива, передаваемых параметру obs.
public static bool InRange<T>(T what, T[] obs) where T : IComparable<T> {
  if(what.CompareTo(obs[0]) < 0 ||
    what.CompareTo(obs[obs.Length-1]) > 0) return false;
  return true;
}

Давайте рассмотрим пример, в котором реализуем простейшую таблицу базы данных, в которой будут определены поля id, login, password пользователя, при этом обеспечим возможность сортировки полей по любому заголовку:

using System;

namespace ConsoleApplication1
{
    // Используем обобщенный интерфейс IComparable
    class DataBase : IComparable<object>
    {
        public string login, password;
        public int id;
        byte sort; // Переменная, принимающая возможные значения типа сортировки
                   // 1 - сортировка по id
                   // 2 - сортировка по логину

        public DataBase() {}

        public DataBase(string login, string password, int id)
        {
            this.login = login;
            this.password = password;
            this.id = id;
        }

        // Реализуем интерфейс IComparable<T>
        public int CompareTo(object other)
        {
            // Проверим тип объекта obj
            DataBase db = other as DataBase;
            if (db != null)
            {
                switch (db.sort)
                {
                    case 1:
                        {
                            if (this.id > db.id)
                                return 1;
                            if (this.id < db.id)
                                return -1;
                            else
                                return 0;
                        }
                    case 2:
                        {
                            if (String.Compare(this.login, db.login) > 0)
                                return 1;
                            if (String.Compare(this.login, db.login) < 0)
                                return -1;
                            else
                                return 0;
                        }
                    default:
                        throw new FormatException("Такой сортировки не существует");
                }
            }
            else
                throw new ArgumentException("Данный параметр не относится к базе данных");
        }

        // Метод, реализующий сортировку
        public void Sort(ref DataBase[] db_object)
        {
            try
            {
                Array.Sort(db_object);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            foreach (DataBase d in db_object)
                Console.WriteLine("{0}\t{1}\t{2}",d.id,d.login,d.password);
        }

        public void NumberSort(byte j, ref DataBase[] arr)
        {
            for (int i = 0; i < arr.Length; i++)
                arr[i].sort = j;
        }
    }

    class Program
    {
        static void Main()
        {
            DataBase[] db_arr = new DataBase[5];
            db_arr[0] = new DataBase("alex85", "sddd", 14);
            db_arr[1] = new DataBase("dm23", "12345", 2);
            db_arr[2] = new DataBase("rvklops", "ss", 5);
            db_arr[3] = new DataBase("Djeff", "sdsdsdf", 86);
            db_arr[4] = new DataBase("dff","s",15);

            Console.WriteLine("Исходная таблица: \n-----------------\n\n" + 
                "ID\tLogin\tPassword\n");
            foreach (DataBase d in db_arr)
                Console.WriteLine("{0}\t{1}\t{2}",d.id,d.login,d.password);

            Console.WriteLine("\nОтсортированная таблица БД по id-пользователя: " +
                "\n----------------------------------------------\n" +
                "ID\tLogin\tPassword\n");
            DataBase sr1 = new DataBase();
            sr1.NumberSort(1, ref db_arr);
            sr1.Sort(ref db_arr);

            Console.WriteLine("\nОтсортированная таблица БД по логину пользователя: " +
                "\n----------------------------------------------\n" +
                "ID\tLogin\tPassword\n");
            sr1.NumberSort(2, ref db_arr);
            sr1.Sort(ref db_arr);
                        
            Console.ReadLine();
        }
    }
}
Реализация обобщенного примера
Пройди тесты
Лучший чат для C# программистов