Почему это общее ограничение компилируется, когда оно имеет циклическую ссылку

Я написал метод расширения в csharp для помощника MVCContrib Html и был удивлен формой общего ограничения, которое, по-видимому, циклически ссылается на себя через параметр типа.

Это говорит о том, что метод компилируется и работает как нужно.

Я бы хотел, чтобы кто-то объяснил, почему это работает, и если существует более интуитивный, интуитивно понятный синтаксис и если нет, если кто-нибудь знает почему?

Здесь приведен код компиляции и кода функции, но я удалил пример списка T, поскольку он затуманил проблему. , а также аналогичный метод с использованием List <T>.

namespace MvcContrib.FluentHtml 
{
  public static class FluentHtmlElementExtensions
  {
    public static TextInput<T> ReadOnly<T>(this TextInput<T> element, bool value)
        where T: TextInput<T>
    {
        if (value)
            element.Attr("readonly", "readonly");
        else
            ((IElement)element).RemoveAttr("readonly");
        return element;
    }
  }
}

удар>

    /*analogous method for comparison*/
    public static List<T> AddNullItem<T>(this List<T> list, bool value) 
        where T : List<T>
    {
        list.Add(null);
        return list;
    }

В первом методе ограничение T: TextInput <T> кажется, все намерения и цели, круговой. Однако, если я закомментирую это, я получу ошибку компилятора:

"Тип 'T' нельзя использовать в качестве параметра типа 'T' в универсальном типе или методе 'MvcContrib.FluentHtml.Elements.TextInput <T>'. Преобразование в бокс или преобразование параметров типа из 'T' в 'MvcContrib.FluentHtml.Elements.TextInput <T>'. "

отсутствует.

и в списке <T> В случае ошибки (ошибок):

"У лучшего совпадения перегруженного метода для System.Collections.Generic.List.Add (T) 'есть недопустимые аргументы Аргумент 1: невозможно преобразовать из '<null>' на 'T' "

Я мог бы представить, что более интуитивное определение будет таким, которое включает 2 типа, ссылку на общий тип и ссылку на ограничивающий тип, например:

public static TextInput<T> ReadOnly<T,U>(this TextInput<T> element, bool value) 
    where U: TextInput<T>

или

public static U ReadOnly<T,U>(this U element, bool value) 
    where U: TextInput<T>

но ни один из них не компилируется.

6 голосов | спросил Simon Francesco 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 03:59:44 +0400 2010, 03:59:44

5 ответов


0

ОБНОВЛЕНИЕ: этот вопрос был основой моего статья в блоге 3 февраля 2011 года . Спасибо за отличный вопрос!


Это законно, не кругово и довольно распространено. Мне лично это не нравится.

Мне это не нравится:

1) Это слишком умно; как вы обнаружили, умный код труден для людей, незнакомых со сложностями системы типов, чтобы интуитивно понять.

2) Это не соответствует моей интуиции того, что «представляет» универсальный тип. Мне нравятся классы для представления категорий вещей и общие классы для представления параметризованных категорий. Для меня ясно, что «список строк» ​​и «список чисел» - это оба вида списков, различающиеся только типом объекта в списке. Мне гораздо менее понятно, что такое «TextInput из T, где T - это TextInput из T». Не заставляй меня думать.

3) Этот шаблон часто используется в попытке навязать ограничение в системе типов, которое на самом деле не применяется в C #. А именно этот:

abstract class Animal<T> where T : Animal<T>
{
    public abstract void MakeFriends(IEnumerable<T> newFriends);
}
class Cat : Animal<Cat>
{
    public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}

Идея заключается в том, что «подкласс Cat of Animal может дружить только с другими кошками».

Проблема в том, что требуемое правило на самом деле не применяется:

class Tiger: Animal<Cat>
{
    public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}

Теперь тигр может подружиться с кошками, но не с тиграми.

Чтобы действительно сделать это в C #, вам нужно сделать что-то вроде:

abstract class Animal 
{
    public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}

где «THISTYPE» - это волшебная новая языковая функция, которая означает, что «переопределяющий класс необходим для заполнения здесь своего собственного типа».

class Cat : Animal 
{
    public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}

class Tiger: Animal
{
    // illegal!
    public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}

К сожалению, это не типобезопасно:

Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});

Если правило гласит: «животное может подружиться с любым другим человеком», тогда животное может подружиться с животными. Но кошка может дружить только с кошками, а не с тиграми! Материал в позициях параметров должен быть действительным контрвариантно ; в этом гипотетическом случае нам потребуется ковариация , что не сработает.

Кажется, я немного отвлекся. Возвращаясь к предмету этого странно повторяющегося паттерна: я пытаюсь использовать этот паттерн только для общих, легко понимаемых ситуаций, подобных тем, которые упоминались в других ответах:

class SortedList<T> where T : IComparable<T>

То есть нам нужно, чтобы каждый T был сопоставим с любым другим T, если у нас есть надежда составить отсортированный список из них.

Чтобы быть помеченным как круговая, в зависимостях должна быть правильная цикличность:

class C<T, U> where T : U where U : T

Интересной областью теории типов (которая в настоящее время плохо обрабатывается компилятором C #) является область некруглых, но бесконечных универсальных типов. Я написал детектор бесконечного типа, но он не попал в компилятор C # 4 и не является приоритетным для возможных гипотетических будущих версий компилятора. Если вы заинтересованы в некоторых примерах бесконечных типов или в некоторых случаях, когда детектор цикла C # портится, см. Мою статью об этом:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond. ASPX

ответил Eric Lippert 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 20:43:22 +0400 2010, 20:43:22
0

Причина заключается в том, что тип TextInput сам по себе имеет такое ограничение.

public abstract class TextInput<T> where T: TextInput<T>{
   //...
}

Также обратите внимание, что TextInput<T> является абстрактным, и единственный способ создать экземпляр такого класса - извлечь его из CRTP- как мода:

public class FileUpload : TextInput<FileUpload> {
}

Метод расширения не будет компилироваться без этого ограничения, поэтому он есть.

Причина, по которой в первую очередь используется CRTP, заключается в том, чтобы включить строго типизированные методы, включающие интерфейс Fluent в классе base , поэтому рассмотрим такой пример:

public abstract class TextInput<T> where T: TextInput<T>{
   public T Length(int length) {
      Attr(length); 
      return (T)this;
   }
}
public class FileUpload : TextInput<FileUpload> {
   FileUpload FileName(string fileName) {
      Attr(fileName);
      return this;
   }
}

Поэтому, когда у вас есть экземпляр FileUpload, Length возвращает экземпляр FileUpload, даже если он определен в базовом классе. Это делает возможным следующий синтаксис:

FileUpload upload = new FileUpload();
upload                      //FileUpload instance
 .Length(5)                 //FileUpload instance, defined on TextInput<T>
 .FileName("filename.txt"); //FileUpload instance, defined on FileUpload

РЕДАКТИРОВАТЬ Чтобы ответить на комментарии ОП о рекурсивном наследовании классов. Это хорошо известный в C ++ шаблон, который называется Curiously Recurring Template Pattern. Прочитайте его здесь . До сегодняшнего дня я не знал, что это возможно в C #. Я подозреваю, что ограничение связано с разрешением использования этого шаблона в C #.

ответил Igor Zevaka 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 04:45:32 +0400 2010, 04:45:32
0

То, как вы его используете, не имеет никакого смысла. Но использование универсального параметра в ограничении на этот же параметр вполне нормально, вот более очевидный пример:

class MySortedList<T> where T : IComparable<T>

Ограничение выражает тот факт, что между объектами типа T должно быть упорядочение, чтобы упорядочить их.

РЕДАКТИРОВАТЬ: я собираюсь деконструировать ваш второй пример, где ограничение на самом деле неправильно, но помогает компилировать.

Код, о котором идет речь:

/*analogous method for comparison*/
public static List<T> AddNullItem<T>(this List<T> list, bool value) 
    where T : List<T>
{
    list.Add(null);
    return list;
}

Причина, по которой он не будет компилироваться без ограничения, заключается в том, что типы значений не могут быть null. List<T> является ссылочным типом, поэтому с помощью where T : List<T> вы заставляете T быть ссылочным типом, который может быть нулевым. Но вы также делаете AddNullItem практически бесполезным, поскольку вы больше не можете вызывать его для List<string> и т. д. Правильное ограничение:

/* corrected constraint so the compiler won't complain about null */
public static List<T> AddNullItem<T>(this List<T> list) 
    where T : class
{
    list.Add(null);
    return list;
}

Примечание: я также удалил второй параметр, который не использовался.

Но вы можете даже удалить это ограничение, если используете default(T), который предусмотрен именно для этой цели, это означает null, когда T является ссылочным типом и все ноль для любой тип значения.

/* most generic form */
public static List<T> AddNullItem<T>(this List<T> list) 
{
    list.Add(default(T));
    return list;
}

Я подозреваю, что вашему первому методу также нужно ограничение типа T : class, но так как у меня не все классы, которые вы используя я не могу сказать наверняка.

ответил Ben Voigt 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 04:10:41 +0400 2010, 04:10:41
0

Я могу только догадываться, что делает код, который вы опубликовали. Тем не менее, я вижу преимущество в ограничении универсального типа, таком как это. Это имело бы смысл (для меня) в любом сценарии, где вам нужен аргумент некоторого типа, который может выполнять определенные операции над аргументами того же типа.

Вот несвязанный пример:

public static IComparable<T> Max<T>(this IComparable<T> value, T other)
    where T : IComparable<T>
{
    return value.CompareTo(other) > 0 ? value : other;
}

Код, подобный этому, позволит вам написать что-то вроде:

int start = 5;
var max = start.Max(6).Max(3).Max(10).Max(8); // result: 10

Пространство имен FluentHtml - это то, что должно вас предупредить, что это и есть намерение кода (чтобы включить цепочку метода вызовы).

ответил Dan Tao 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 04:38:56 +0400 2010, 04:38:56
0
public static TextInput<T> ReadOnly<T>(this TextInput<T> element, bool value)
    where T: TextInput<T>

Давайте разберемся с этим:

TextInput<T> - это тип возвращаемого значения.

TextInput<T> - это расширяемый тип (тип первого параметра для статического метода)

ReadOnly<T> - это имя функции, которая расширяет тип, определение которого включает в себя T, т.е. TextInput<T>

where T: TextInput<T> - ограничение на T из ReadOnly<T>, так что T можно использовать в общем TextInput<TSource>. (T это TSource!)

Я не думаю, что это круглая.

Если вы снимите ограничение, я ожидаю, что element пытается привести к универсальному типу (а не к TextInput из универсальный тип), который явно не будет работать.

ответил Jeff Meatball Yang 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 24 Sep 2010 05:04:42 +0400 2010, 05:04:42

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132