В википедии, в статье, посвященной обобщенному програмированию, как пример, приведен кусочек кода на C#:
// На платформе .NET средства обобщённого программирования появились в версии 2.0.interface IPerson
{
string GetFirstName();
string GetLastName();
}
class Speaker
{
public void SpeakTo<T>(T person) where T : IPerson
{
string name = person.GetFirstName();
this.say("Hello, " + name);
}
}
Сам по себе смысл ясен, но непонятно, чем это лучше этого:
interface IPerson
{
string GetFirstName();
string GetLastName();
}
class Speaker
{
public void SpeakTo(IPerson person)
{
string name = person.GetFirstName();
this.say("Hello, " + name);
}
}
Здравствуйте, dmitry_npi, Вы писали:
_>В википедии, в статье, посвященной обобщенному програмированию, как пример, приведен кусочек кода на C#:
_>Сам по себе смысл ясен, но непонятно, чем это лучше этого:
Здравствуйте, Ахмед, Вы писали:
_>>В википедии, в статье, посвященной обобщенному програмированию, как пример, приведен кусочек кода на C#: _>>Сам по себе смысл ясен, но непонятно, чем это лучше этого: А>Ничем…
Строго говоря, разница конечно же есть. Достаточно рассмотреть случай, когда IPerson реализуется value-типом.
А>… это плохой пример.
+1
Help will always be given at Hogwarts to those who ask for it.
Тут мы теряем информацию и о типе элемента списка и о типе самого списка.
Вот тот же интерфейс в стиле актуальной версии стандартной библиотеки:
public interface IList<T>
{
bool IsEmpty { get; }
T Head { get; }
IList<T> Tail { get; }
IList<T> Add(T x);
}
Теперь информацию о типе элемента мы не теряем, но информация о типе списка после приведения к интерфейсу потеряна.
А тут мы берем от дженериков все что можно:
public interface IList<T, out TL>
where TL : IList<T,TL>
{
bool IsEmpty { get; }
T Head { get; }
TL Tail { get; }
TL Add(T x);
}
Теперь мы не потеряем информацию о типах.
public static class ListHelper
{
// тут на выходе список именно того типа, который мы получили на вход public static TL AddRange<T, TL>(this TL list, IEnumerable<T> range)
where TL : IList<T, TL>
{
return range.Aggregate(list, (xs, x) => xs.Add(x));
}
// тут мы утратили информацию о типе спискаpublic static IList<T> AddRange<T>(this IList<T> list, IEnumerable<T> range)
{
return range.Aggregate(list, (xs, x) => xs.Add(x));
}
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, matumba, Вы писали:
M>>public void SpeakTo<T>(T person) where T : IPerson, INamedPerson, IPersonWhoHasMouth ... M>>Так смысл генериков проявляется лучше.
K>По моему, смысл дженериков проявляется лучше, если нам нужно не только передавать что-то, но и возвращать. K>Вот интерфейс списка а-ля C#1.0: K>
K>Тут мы теряем информацию и о типе элемента списка и о типе самого списка. K>Вот тот же интерфейс в стиле актуальной версии стандартной библиотеки: K>
K>Теперь информацию о типе элемента мы не теряем, но информация о типе списка после приведения к интерфейсу потеряна. K>А тут мы берем от дженериков все что можно: K>
K>public interface IList<T, out TL>
K> where TL : IList<T,TL>
K>{
K> bool IsEmpty { get; }
K> T Head { get; }
K> TL Tail { get; }
K> TL Add(T x);
K>}
K>
K>Теперь мы не потеряем информацию о типах. K>
K>public static class ListHelper
K>{
K> // тут на выходе список именно того типа, который мы получили на вход
K> public static TL AddRange<T, TL>(this TL list, IEnumerable<T> range)
K> where TL : IList<T, TL>
K> {
K> return range.Aggregate(list, (xs, x) => xs.Add(x));
K> }
K> // тут мы утратили информацию о типе списка
K> public static IList<T> AddRange<T>(this IList<T> list, IEnumerable<T> range)
K> {
K> return range.Aggregate(list, (xs, x) => xs.Add(x));
K> }
K>}
K>
Самый лучший пример с дженериками — это сортировка А там я так написал, специфичную для дженерика вещь.
Здравствуйте, Klapaucius, Вы писали:
K>А тут мы берем от дженериков все что можно: K>
K>public interface IList<T, out TL>
K> where TL : IList<T,TL>
K>{
K> bool IsEmpty { get; }
K> T Head { get; }
K> TL Tail { get; }
K> TL Add(T x);
K>}
K>
Ого... Я такие чтуки даже в сети не видел.
Их специально придумали для ко-контравариаций? Что-то ещё полезное они делают?
Klapaucius, и ещё вопросик: а как подставляется аргумент в этот IList<T, out TL>? (если этот TL похож на рекурсивный тип)
Здравствуйте, Константин Л., Вы писали:
КЛ>Здравствуйте, dmitry_npi, Вы писали:
_>>В википедии, в статье, посвященной обобщенному програмированию, как пример, приведен кусочек кода на C#:
_>>
_>>// На платформе .NET средства обобщённого программирования появились в версии 2.0.
_>>interface IPerson
_>>{
_>> string GetFirstName();
_>> string GetLastName();
_>>}
_>>class Speaker
_>>{
_>> public void SpeakTo<T>(T person) where T : IPerson
_>> {
_>> string name = person.GetFirstName();
_>> this.say("Hello, " + name);
_>> }
_>>}
_>>
КЛ>afaik, тут компайлер может соптимизировать вызов GetFirstName для некоторых T
Вернее может проявиться эффект от реализации дженериков в CLR
Если T будет типом-значением, то вызов GetFirstName() будет происходить напрямую, не через интерфейс, и даже может успешно заинланиться. Это всё из-за того, что чтобы вызвать интерфейсный метод, структуру пришлось бы боксить, что неприемлимо в generic-коде.
[]
П>Вернее может проявиться эффект от реализации дженериков в CLR П>Если T будет типом-значением, то вызов GetFirstName() будет происходить напрямую, не через интерфейс, и даже может успешно заинланиться. Это всё из-за того, что чтобы вызвать интерфейсный метод, структуру пришлось бы боксить, что неприемлимо в generic-коде.
для value-типов ясно. мне казалось, что для остальных вызов может быть вызван напрямую (без callvirt?), а не через интерфейс, но могу ошибаться
Здравствуйте, Пельмешко, Вы писали:
П>Это всё из-за того, что чтобы вызвать интерфейсный метод, структуру пришлось бы боксить, что неприемлимо в generic-коде.
Гхм: почему не преемлемо? Мне казалось, что именно так вызываются методы интерфейсов, реализованные в структуре явно. Разве нет?
И, кстати, решарпер, кажется, предупреждает о возможном боксинге при сравнении переменной дженерик-типа без ограничения struct/class c null, тогда как Рихтер утверждал, что такое сравнение нормально переварится джитом и не нужный (заранее известно, что ложный) код попросту не будет скомпилирован — кто прав тут
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, matumba, Вы писали:
M>Ого... Я такие чтуки даже в сети не видел.
А я видел даже библиотеку, в которой интерфейс списка примерно так и выглядит.
M>Их специально придумали для ко-контравариаций? Что-то ещё полезное они делают?
Да, out в данном случае указатель ковариантности. Но он в данном случае не обязателен — все и без него работает.
M>а как подставляется аргумент в этот IList<T, out TL>? (если этот TL похож на рекурсивный тип)
public class List<T> : IList<T, List<T>>
{
public bool IsEmpty { ... }
public T Head { ... }
public List<T> Tail { ... }
public List<T> Add(T x) { ... }
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Тут мы теряем информацию и о типе элемента списка и о типе самого списка. K>Вот тот же интерфейс в стиле актуальной версии стандартной библиотеки:
K>Теперь информацию о типе элемента мы не теряем, но информация о типе списка после приведения к интерфейсу потеряна. K>А тут мы берем от дженериков все что можно:
K>public interface IList<T, out TL>
K> where TL : IList<T,TL>
K>{
K> bool IsEmpty { get; }
K> T Head { get; }
K> TL Tail { get; }
K> TL Add(T x);
K>}
K>Теперь мы не потеряем информацию о типах.
А зачем оно ("информация о типе списка") нужно, по большому-то счёту? AddRange из ListHelper-а можно реализовать и без этого. В каких сценариях первого объявления списка не достаточно и требуется вторая?
Help will always be given at Hogwarts to those who ask for it.
. Инлайн работает на CLR 2 32 и CLR 4 32 и 64. Сервиспаки для второй версии привносят некоторые нюансы: пример по ссылке, кажется, так быстро работать уже не будет. Проблема со статической переменной.
... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, ylem, Вы писали:
_FR>>Строго говоря, разница конечно же есть. Достаточно рассмотреть случай, когда IPerson реализуется value-типом.
Y>Типа, компилятор умный и боксинга не случится?
Здравствуйте, _FRED_, Вы писали:
_FR>А зачем оно ("информация о типе списка") нужно, по большому-то счёту? AddRange из ListHelper-а можно реализовать и без этого. В каких сценариях первого объявления списка не достаточно и требуется вторая?
Допустим, что у нас есть дек, позволяющая добавлять элементы и в другой конец:
public interface IDeque<T, out TD> : IList<T, TD>
where TD : IDeque<T,TD>
{
T Last { get; }
TL Init { get; }
TL Append(T x);
}
Для дек AddRange без потери информации о типе будет работать так же хорошо, как и для списка. А на выходе метода с потерей мы останемся без методов добавления элементов в другой конец или будем вынуждены писать даун-каст.
... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll