C# универсальные объявления, ссылающиеся на себя
Я читал Альбахариса "C# 5.0 в двух словах", и я столкнулся с этим в разделе "Общие", и это называется законным:
class Bar<T> where T : Bar<T> { ... }
И это ничего не значит для меня, хотя я внимательно прочитал всю главу. Я не мог понять даже немного этого.
Может кто-нибудь объяснить, пожалуйста, с некоторыми понятными именами, например:
class Person<T> where T : Person<T> { ... }
И сценарий реального применения, где это использование уместно и полезно?
2 ответа
Это означает, что T
должен наследовать от Person<T>
,
Это типичный способ создания специфичных для типа методов или свойств или параметров в базовом классе, специфичных для фактического потомка.
Например:
public abstract class Base<T> where T : Base<T>, new()
{
public static T Create()
{
var instance = new T();
instance.Configure(42);
return instance;
}
protected abstract void Configure(int value);
}
public class Actual : Base<Actual>
{
protected override void Configure(int value) { ... }
}
...
Actual a = Actual.Create(); // Create is defined in Base, but returns Actual
Это полезно, когда вы работаете с какой-то внешней библиотекой или структурой (которую вы не можете или не хотите изменять). Например, у вас есть класс User
из этой библиотеки и определенно разработчик, который будет ее использовать, определит CustomUser
класс, который наследуется от него (просто для добавления некоторых пользовательских полей). Также давайте представим, что User
Класс имеет некоторые ссылки на других пользователей, например: создатель и deletor (которые, очевидно, будут экземплярами CustomUser
тип). И в этом случае обобщенная декларация, ссылающаяся на себя, может очень хорошо помочь. Мы передадим тип потомка (CustomUser
) как параметр для базы (User
) класс, так на User
Объявление класса мы можем установить типы создателя и deletor точно так, как они будут в будущем (CustomUser
), поэтому кастинг не понадобится:
public class User<TCustomUser> where TCustomUser : User<TCustomUser>
{
public TCustomUser creator {get;set;}
public TCustomUser deletor {get;set;}
//not convenient variant, without generic approach
//public User creator {get;set;}
//public User deletor {get;set;}
}
public class CustomUser : User<CustomUser>
{
//custom fields:
public string City {get;set;}
public int Age {get;set;}
}
Использование:
CustomUser customUser = getUserFromSomeWhere();
//we can do this
var creatorsAge = customUser.creator.Age;
//without generic approach:
//var creatorsAge = ((CustomUser)customUser.creator).Age;
Возможно, я немного опоздал на вечеринку, но я хочу поделиться нереальным сценарием приложения для развлечения:)
// self referencing list in c#
// we cant use generic type syntax: new List<List<List..
// but dynamic keyword comes to save us
var list = new List<dynamic>();
list.Add(list); // the "FBI! open up" part
Console.WriteLine(list[0][0][0][0][0][0][0].Count); // 1
Это также полезно, когда у вас есть ряд классов для написания и вы понимаете, что 80% (выберите число) кода по существу одинаковы, за исключением того, что они зависят от ТИПА.
Написание универсального кода позволяет вам захватить весь этот повторяющийся код в базовом классе и повторно использовать его.
Конкретный шаблон, приведенный выше, важен / необходим, потому что вы хотите, чтобы T был классом, который вы пытаетесь написать.
Представьте себе структуру, в которой объект crud основан на crudBase и все наследуется от него. Далее представьте, что у вас есть базовый класс, который помогает вам запрашивать эти объекты (queryBase), и будет 1:1 с классами crudBase и queryBase.
Сделать queryBase универсальным просто, потому что довольно очевидно, как вы его объявите
public abstract class queryBase<T> where T : crudBase
{
public list<T> FindMatches(string SearchCriteria){}
}
Без универсального типа это должно быть в каждом конкретном классе, поскольку тип возвращаемого значения изменяется. Дженерики потрясающие.
что немного менее очевидно, так это то, как достичь того же уровня ОБЩЕЙ нирваны с crudBase. Предположим, у вас есть 70% стандартного кода CRUD уже в подклассе, но есть еще 10%, где логика должна ссылаться на тип.
(выберите число, цифры в% не важны)
Решение GENERIC здесь менее очевидно. В первом случае вы GENERIC класс ссылается на другой класс с Т. В этом случае, вы хотите сослаться на один и тот же класс с Т.
Фактически, используя описанный выше шаблон, вы можете добиться этого:
public class crudBaseGeneric<T> where T : crudBaseGeneric<T>
{
public <T> CopyMe(){}
}
Здесь вы переопределите свой базовый класс как универсальный, и вы сможете захватить эти последние 10%.
Опять же, без дженериков я должен скопировать и вставить мою функцию CopyMe() в каждый конкретный класс.