1) У вас новый перл в статье появился: «Поэтому если упаковать свой тип в Object и пытаться использовать его в словаре то ничего хорошего из этого не выйдет».
Я вот совершенно не понимаю, как можно упаковать свой тип в Object. Не подскажите?
2) Пример не из жизни, если вам надо получить множество всех машин, а не множество всех машин + случаный год выпуска, как у вас, то обычно пишут что-то такое: customTypeLongList.Select(x => x.Name).Intersect(customTypeShortList.Select(x => x.Name));
3) Про GetHashCode. Вообще, складываение в хэши — это настолько частая операция, особенно в бизнес-приложениях, что иметь для этого все под рукой — очень логичная задача. Более того, создавая отдельный специализированный comparer вы нарушаете инкапсуляцию, потому что только сам объект знает о своей внутренней структуре и знает как себя сравнивать на равенство и проч. (особенно это очевидно, когда у вас может быть несколько инстансов объекта, которые представляют один и тот же объект реального мира). И именно поэтому GetHashCode у Object никак не зависит от данных объекта, к тому же как вы будите считать тогда хэш-код, например, у узла графа или двунаправленного списка?
Внешний comparer бывает полезен, когда вы смотрите под объекты под разным углом, например: равенство и похожесть (похожие объекты вы, например, хотите на экране пользователю подсвечивать одним цветом).
Вы же в своем примере реализуете такой comparer похожести в самом объекте, что как бы намекает пользователю, что все машины с одинаковым названием и разным годом выпуска одинаковы. Это примерно как все Иван Петровчи — одно и то же лицо. Вот если бы у вас был внешний компарер (к тому же как смущающе выглядит new CustomType() в Intersect), который назывался как-нибудь типа OnlyNameComparer — было бы понятно и логично.
4) И ваш вариант:
public int GetHashCode(CustomType obj)
{
return string.Empty.GetHashCode();
}
Еще какой верный. Только он превратит операцию поиска в хэше из O(1) в O(n), со всеми вытекающими взрывами сложности сопряженных алгоритмов. Intersect, например, сразу станет занимать O(n^2). А это выстрелит в ногу уже на сотне элементов.
5) Если вы не далаете выводов, то зачем вы написали всю эту статью?
1) Почему у вас в CustomType не переопределен метод GetHashCode?
Особенно в свете (в первом варианте)
public int GetHashCode(CustomType obj)
{
return obj.GetHashCode();
}
Это ж будет жуткий втык.
2) А вы считаете, что две Оли с разными возрастами — это одна Оля? Пример конечно же в таком случае удачнейший, из реальной жизни, и прозрачный.
3) И это не холивар. Вы маскируете под кажущейся простотой и очевидностью важные вещи. Крадя у ума пищу и провоцируя будущие ошибки у наивного читателя.
4) И вы делаете выводы. Вы ж целую статью выводов из своих наблюдений написали.
5) Если вы нормально напишите о применимости метода GetHashCode, а он применяется в хеш-таблицах и больше почти нигде, то статья будет вменяемая.
А вся эта лабуда про приоритеты — это ж кто-то подумает, что объекты с равными хэш-кодами считаются равными. Никакого приоритета в сравнении нет, и GetHashCode используется не для сравнения, а для поиска.
И потом, кто мешает возвращать в GetHashCode констунту? И как правильно переопределять GetHashCode, если он дожен считаться для группы полей?
1) Я знаю, что такое Рефлектор. Исходники фрейморка же доступны через MS Source Server: Tools -> Options -> Debuggings -> General -> Enable Source server support ну и далее в хелп, про то как этим пользоваться.
2) Я добивась того, чтобы вы либо исправили, либо удалили безграмотную статью. А безграмотна он ересью, которую я процитировал в самом начале:
«Так как GetHashCode при сравнении имеет больший приоритет перед Equals»
«Наверное такое поведение было сделано для улучшения производительности»
Вы делаете выводы при совершенном незнакомстве с базовыми вещами и при полном непонимании процессов, которые происходят внутри.
1) Во-первых, хэш-таблица эта основной (и единственный известный широкой публике) метод проверить наличие элемента в массиве за O(1). Во-вторых, исходники фреймворка конечно же есть и свободно доступны почти в полном виде:
public static IEnumerable<TSource> Intersect<TSource>([NotNull] this IEnumerable<TSource> first, [NotNull] IEnumerable<TSource> second, [CanBeNull] IEqualityComparer<TSource> comparer)
{
if (first == null)
throw new ArgumentNullException(«first»);
if (second == null)
throw new ArgumentNullException(«second»);
return IntersectImpl(first, second, comparer);
}
private static IEnumerable<TSource> IntersectImpl<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
var secondSet = new HashSet<TSource>(second, comparer);
foreach (var element in first)
{
if (secondSet.Remove(element))
yield return element;
}
}
2) В том куске MSDN, который вы выдрали из контекста, написано только лишь то, что реализация GetHashCode _по-умолчанию_ из Object — плохая реализация, потому что не даст в итоге должного распределения, и приведет к операции поиска стоимостью O(N).
И там же: The GetHashCode method is suitable for use in hashing algorithms and data structures such as a hash table.
И во всем фреймворке этот метод используется именно и только так.
1) Вы все-таки определитесь, присутствуют они в тексте статьи или не присутствуют, я про Hashtable и Dictionary, и вообще все контейнеры на основе хэш-таблиц.
2) хэш-таблица у вас есть в методе Intersect (это, конечно, детали реализации, но IEqualityComparer в любом случае должен быть консистентным)
3) Вы все-таки не понимаете и не прочитали, что такое хэш-таблица. GetHashCode — метод возвращающий хэш-код объекта. Хэш-таблица — это характеристика контейнера, а не объекта. Если вы переопределяете GetHashCode, то вы переопределяете хэш-код объекта. Будите ли вы его складывать в хэш или нет — это уже следующий этап. Но сам метод GetHashCode подразумевает совершенно единственное его применение — возвращение хэш-кода для хэшей. Все, если метод возвращает лабуду, то хэши не работают, что ваш пример и демонстрирует.
И к .Net это вообще не имеет отношения. Это фундаментальное свойство хэш-контейнетров.
У вас же в голове какой-то безумный мусор из всех понятий.
хэш-код используется в хэш-таблицах для быстрого поиска элементов
хэш-таблицы устроены таким образом, что у равных элементов должен быть одинаковый хэш-код
хэш-код не должен меняться в течении жизни объекта
все. и никаких оптимизаций, приоритетов, улучшений производительности и связи с .Net
Object — это пустой объект
List — это список
Хэш-таблица — это хэш-таблица
Массив — это массив
GetHashCode — метод, возвращающий хэш-код данного объекта
и возьмите Кнута или книжку с осликами и прочитайте, что такое хэш-таблица, зачем в ней хэш-код и все прочие сопутствующие штуки
вы мне прокомментируйте предложение из вашего поста: «Наверное такое поведение было сделано для улучшения производительности», а то мне как-то не хватает фактического материала продолжить дискуссию.
А Equals не вызывается, потому что эти многие разработчики не знают, что такой хэш-таблица. И .Net тут, ну поверьте, совершенно не при чем. Ну, вот ни разу. Даже приблизительно. И даже MSDN не при чем, в котором конечно же ни слова про то, что такое хэш-таблица нет.
я хочу сказать, что хэш-таблица — это совершенно определенным образом (вообще, т.е. безотносительно .net) устроенная структура данных, из свойств которой очевидно следуют все те выводы, к которым вы пришли, и еще некоторые другие.
вы же, предлагая читателю недопустимые определения «наверное такое поведение было сделано» и прочее, вводите последнего в заблуждение относительно устройства мира, демонстрируя параллельно незнание фундаментальных вещей из области алгоритмов и структур данных.
верные выводы на катастрофически неверном материале и на фантастических же предположениях — это путь в полное никуда.
а зачем? GC во-первых сам сделает это тогда, когда ему удобно. К тому же если у вас много всякой всячины во втором поколении — это элементарно воткнет.
А если в программе есть какие-то кэши на week? Этот код тогда тем более добавит тормозов.
Если вы боитесь Out Of Memory, то GC всегда делает последнюю попытку перед тем как кинуть исключение.
var element = GetSelectedElement(false);
if (element == null)
return false;
var parent = element.ToTreeNode().Parent;
if (parent == null || parent.GetType().Name != «ReferenceName» || parent.Parent == null
|| string.IsNullOrEmpty(parent.Parent.GetText()))
return false;
string s = parent.Parent.GetText();
if (string.IsNullOrEmpty(s))
return false;
Можно просто написать:
var element = GetSelectedElement(true);
if(element == null) return false;
var s = element.ShortName;
Но еще правильнее сделать так:
var und = GetSelectedElement(true); // проверяем что мы внутри using-директивы
if(und == null) return false;
if(und.ImportedNamespace != null) return false; // проверяем, что namesapce красный
var s = und. ImportedNamespaceReference .ShortName;
И потом, isAvailable должен быть быстрым. Поэтому код, который лезет в базу данных хорошо бы перенести в свойсво Items, тогда этот когда будет вызываться только если пользователь открывает меню по alt-enter.
И, кстати, можно все это сделать QuickFix-ом, потому что демон уже сам делает проверку всех ссылок, а ваш CA как раз должен возбуждаться только на ошибки:
[QuickFix]
class MyQF: IQuickFix
{
public MyQF(NotResolvedError error)
{
rn = error.Reference.GetElement() as IReferenceName;
// потом в IsAvailable проверить: rn.GetContainignElement(true) != null;
}
В R# 5.0 есть встроенный просмотрщик дерева, которое строит решарпер. Он устанавливается обычным инсталлятором и интегрирован в дебаггер студии. Соответственно, подсунув ему любой элемент из дерева, можно посмотреть как там все на самом деле устроено.
Взял я эту информацию из личного опыта разработки R#. На самом деле, если вы действительно заинтересованы в разработке плагинов (для себя или на благо общества), то не стесняйтесь спрашивать тут: www.jetbrains.net/devnet/community/resharper
Там, правда, принято общаться по-английски. В любом случае можете написать прямо мне. Мы с удовольствием отвечаем на вопросы наших пользователей.
Еще по поводу вашего примера хочу добавить две вещи: ToTreeNode никогда не возвращает null. Методы CSharpElementFactory никогда не возвращают null, а в случае ошибки бросают исключение. Зная эти вещи, вы можете сильно улучшить читаемость кода, убрав лишние проверки.
1) В R# уже есть базовый класс дня написания Context Actions. Это класс BulbItemImpl. Его можно использовать примерно так:
class MyCA : BulbItemImpl, IContextAction
{
private readonly ICSharpContextActionDataProvider myProvider;
protected MyCa(ICSharpContextActionDataProvider provider)
{
myProvider = provider;
}
public bool IsAvailable(IUserDataHolder cache)
{
return ...;
}
public override string Text
{
get { return "My menu item text"; }
}
protected override Action<ITextControl> ExecuteTransaction(ISolution solution, IProgressIndicator progress)
{
//Do Something
//here you can return 'null'
return control =>
{
//Do something with caret, selection, etc.
//Ex: control.Caret.MoveTo(myElement.GetDocumentRange().TextRange.StartOffset);
};
}
}
Плюсы BulbItemImpl в том, что он будет корректно работать в случае изменений в нескольких файлах (например, вы меняете что-то в иерархии). Для долгих операций этот класс будет показывать окошко с прогресом. А так, конечно, разницы большой нет.
2) using (ReadLockCookie.Create()) {… } — делать в CA не надо
3) Использовать LowLevelModificationUtil нельзя. На то они и LowLevel. В этом просто примере это работет (хотя в R# 5 вообще-то должно упасть с исключением), но в более сложных случаях разрушит цивилизацию. Вместо этого надо использовать метод ICSharpExpression.ReplaceBy. И вообще, обычно все узлы синтаксического дерева снабнажены методами для изменения самих себя.
4) Лучше стараться не переходить к TreeNode (т.е. вызывать метод ToTreeNode), которые содержат в себе уже не только семантическую информацию, а еще и синтаксическую. Например, в давнном CA все вызовы ToTreeNode избыточны. Но это замечаение больше по стилю: интерфейс TreeNode менее стабилен и менее понятен (много синтаксического «мусора»).
Я вот совершенно не понимаю, как можно упаковать свой тип в Object. Не подскажите?
2) Пример не из жизни, если вам надо получить множество всех машин, а не множество всех машин + случаный год выпуска, как у вас, то обычно пишут что-то такое: customTypeLongList.Select(x => x.Name).Intersect(customTypeShortList.Select(x => x.Name));
3) Про GetHashCode. Вообще, складываение в хэши — это настолько частая операция, особенно в бизнес-приложениях, что иметь для этого все под рукой — очень логичная задача. Более того, создавая отдельный специализированный comparer вы нарушаете инкапсуляцию, потому что только сам объект знает о своей внутренней структуре и знает как себя сравнивать на равенство и проч. (особенно это очевидно, когда у вас может быть несколько инстансов объекта, которые представляют один и тот же объект реального мира). И именно поэтому GetHashCode у Object никак не зависит от данных объекта, к тому же как вы будите считать тогда хэш-код, например, у узла графа или двунаправленного списка?
Внешний comparer бывает полезен, когда вы смотрите под объекты под разным углом, например: равенство и похожесть (похожие объекты вы, например, хотите на экране пользователю подсвечивать одним цветом).
Вы же в своем примере реализуете такой comparer похожести в самом объекте, что как бы намекает пользователю, что все машины с одинаковым названием и разным годом выпуска одинаковы. Это примерно как все Иван Петровчи — одно и то же лицо. Вот если бы у вас был внешний компарер (к тому же как смущающе выглядит new CustomType() в Intersect), который назывался как-нибудь типа OnlyNameComparer — было бы понятно и логично.
4) И ваш вариант:
public int GetHashCode(CustomType obj)
{
return string.Empty.GetHashCode();
}
Еще какой верный. Только он превратит операцию поиска в хэше из O(1) в O(n), со всеми вытекающими взрывами сложности сопряженных алгоритмов. Intersect, например, сразу станет занимать O(n^2). А это выстрелит в ногу уже на сотне элементов.
5) Если вы не далаете выводов, то зачем вы написали всю эту статью?
1) Почему у вас в CustomType не переопределен метод GetHashCode?
Особенно в свете (в первом варианте)
public int GetHashCode(CustomType obj)
{
return obj.GetHashCode();
}
Это ж будет жуткий втык.
2) А вы считаете, что две Оли с разными возрастами — это одна Оля? Пример конечно же в таком случае удачнейший, из реальной жизни, и прозрачный.
3) И это не холивар. Вы маскируете под кажущейся простотой и очевидностью важные вещи. Крадя у ума пищу и провоцируя будущие ошибки у наивного читателя.
4) И вы делаете выводы. Вы ж целую статью выводов из своих наблюдений написали.
5) Если вы нормально напишите о применимости метода GetHashCode, а он применяется в хеш-таблицах и больше почти нигде, то статья будет вменяемая.
А вся эта лабуда про приоритеты — это ж кто-то подумает, что объекты с равными хэш-кодами считаются равными. Никакого приоритета в сравнении нет, и GetHashCode используется не для сравнения, а для поиска.
И потом, кто мешает возвращать в GetHashCode констунту? И как правильно переопределять GetHashCode, если он дожен считаться для группы полей?
2) Я добивась того, чтобы вы либо исправили, либо удалили безграмотную статью. А безграмотна он ересью, которую я процитировал в самом начале:
«Так как GetHashCode при сравнении имеет больший приоритет перед Equals»
«Наверное такое поведение было сделано для улучшения производительности»
Вы делаете выводы при совершенном незнакомстве с базовыми вещами и при полном непонимании процессов, которые происходят внутри.
public static IEnumerable<TSource> Intersect<TSource>([NotNull] this IEnumerable<TSource> first, [NotNull] IEnumerable<TSource> second, [CanBeNull] IEqualityComparer<TSource> comparer)
{
if (first == null)
throw new ArgumentNullException(«first»);
if (second == null)
throw new ArgumentNullException(«second»);
return IntersectImpl(first, second, comparer);
}
private static IEnumerable<TSource> IntersectImpl<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
var secondSet = new HashSet<TSource>(second, comparer);
foreach (var element in first)
{
if (secondSet.Remove(element))
yield return element;
}
}
2) В том куске MSDN, который вы выдрали из контекста, написано только лишь то, что реализация GetHashCode _по-умолчанию_ из Object — плохая реализация, потому что не даст в итоге должного распределения, и приведет к операции поиска стоимостью O(N).
И там же: The GetHashCode method is suitable for use in hashing algorithms and data structures such as a hash table.
И во всем фреймворке этот метод используется именно и только так.
2) хэш-таблица у вас есть в методе Intersect (это, конечно, детали реализации, но IEqualityComparer в любом случае должен быть консистентным)
3) Вы все-таки не понимаете и не прочитали, что такое хэш-таблица. GetHashCode — метод возвращающий хэш-код объекта. Хэш-таблица — это характеристика контейнера, а не объекта. Если вы переопределяете GetHashCode, то вы переопределяете хэш-код объекта. Будите ли вы его складывать в хэш или нет — это уже следующий этап. Но сам метод GetHashCode подразумевает совершенно единственное его применение — возвращение хэш-кода для хэшей. Все, если метод возвращает лабуду, то хэши не работают, что ваш пример и демонстрирует.
И к .Net это вообще не имеет отношения. Это фундаментальное свойство хэш-контейнетров.
У вас же в голове какой-то безумный мусор из всех понятий.
хэш-таблицы устроены таким образом, что у равных элементов должен быть одинаковый хэш-код
хэш-код не должен меняться в течении жизни объекта
все. и никаких оптимизаций, приоритетов, улучшений производительности и связи с .Net
это элементарные алгоритмические вещи
List — это список
Хэш-таблица — это хэш-таблица
Массив — это массив
GetHashCode — метод, возвращающий хэш-код данного объекта
и возьмите Кнута или книжку с осликами и прочитайте, что такое хэш-таблица, зачем в ней хэш-код и все прочие сопутствующие штуки
А Equals не вызывается, потому что эти многие разработчики не знают, что такой хэш-таблица. И .Net тут, ну поверьте, совершенно не при чем. Ну, вот ни разу. Даже приблизительно. И даже MSDN не при чем, в котором конечно же ни слова про то, что такое хэш-таблица нет.
вы же, предлагая читателю недопустимые определения «наверное такое поведение было сделано» и прочее, вводите последнего в заблуждение относительно устройства мира, демонстрируя параллельно незнание фундаментальных вещей из области алгоритмов и структур данных.
верные выводы на катастрофически неверном материале и на фантастических же предположениях — это путь в полное никуда.
«Наверное такое поведение было сделано для улучшения производительности. „
по-моему автор не совсем представляет что такое хеш-таблица, почему, зачем и как она устроена.
А если в программе есть какие-то кэши на week? Этот код тогда тем более добавит тормозов.
Если вы боитесь Out Of Memory, то GC всегда делает последнюю попытку перед тем как кинуть исключение.
var element = GetSelectedElement(false);
if (element == null)
return false;
var parent = element.ToTreeNode().Parent;
if (parent == null || parent.GetType().Name != «ReferenceName» || parent.Parent == null
|| string.IsNullOrEmpty(parent.Parent.GetText()))
return false;
string s = parent.Parent.GetText();
if (string.IsNullOrEmpty(s))
return false;
Можно просто написать:
var element = GetSelectedElement(true);
if(element == null) return false;
var s = element.ShortName;
Но еще правильнее сделать так:
var und = GetSelectedElement(true); // проверяем что мы внутри using-директивы
if(und == null) return false;
if(und.ImportedNamespace != null) return false; // проверяем, что namesapce красный
var s = und. ImportedNamespaceReference .ShortName;
И потом, isAvailable должен быть быстрым. Поэтому код, который лезет в базу данных хорошо бы перенести в свойсво Items, тогда этот когда будет вызываться только если пользователь открывает меню по alt-enter.
И, кстати, можно все это сделать QuickFix-ом, потому что демон уже сам делает проверку всех ссылок, а ваш CA как раз должен возбуждаться только на ошибки:
[QuickFix]
class MyQF: IQuickFix
{
public MyQF(NotResolvedError error)
{
rn = error.Reference.GetElement() as IReferenceName;
// потом в IsAvailable проверить: rn.GetContainignElement(true) != null;
}
…
}
На английском есть вступительный документ с картинками: www.jetbrains.com/resharper/documentation/reviewers_guide.html
Есть keyboard map — емко, информативно, по делу, рекомендую всем иметь под рукой: www.jetbrains.com/resharper/documentation/documentation.html
И есть прекрасный цикл статей «31 Days of Resharper»: blog.excastle.com/2007/01/31/blog-event-the-31-days-of-resharper/
Там, правда, принято общаться по-английски. В любом случае можете написать прямо мне. Мы с удовольствием отвечаем на вопросы наших пользователей.
Еще по поводу вашего примера хочу добавить две вещи: ToTreeNode никогда не возвращает null. Методы CSharpElementFactory никогда не возвращают null, а в случае ошибки бросают исключение. Зная эти вещи, вы можете сильно улучшить читаемость кода, убрав лишние проверки.
Удачи в разработке! )
1) В R# уже есть базовый класс дня написания Context Actions. Это класс BulbItemImpl. Его можно использовать примерно так:
class MyCA : BulbItemImpl, IContextAction { private readonly ICSharpContextActionDataProvider myProvider; protected MyCa(ICSharpContextActionDataProvider provider) { myProvider = provider; } public bool IsAvailable(IUserDataHolder cache) { return ...; } public override string Text { get { return "My menu item text"; } } protected override Action<ITextControl> ExecuteTransaction(ISolution solution, IProgressIndicator progress) { //Do Something //here you can return 'null' return control => { //Do something with caret, selection, etc. //Ex: control.Caret.MoveTo(myElement.GetDocumentRange().TextRange.StartOffset); }; } }Плюсы BulbItemImpl в том, что он будет корректно работать в случае изменений в нескольких файлах (например, вы меняете что-то в иерархии). Для долгих операций этот класс будет показывать окошко с прогресом. А так, конечно, разницы большой нет.
2) using (ReadLockCookie.Create()) {… } — делать в CA не надо
3) Использовать LowLevelModificationUtil нельзя. На то они и LowLevel. В этом просто примере это работет (хотя в R# 5 вообще-то должно упасть с исключением), но в более сложных случаях разрушит цивилизацию. Вместо этого надо использовать метод ICSharpExpression.ReplaceBy. И вообще, обычно все узлы синтаксического дерева снабнажены методами для изменения самих себя.
4) Лучше стараться не переходить к TreeNode (т.е. вызывать метод ToTreeNode), которые содержат в себе уже не только семантическую информацию, а еще и синтаксическую. Например, в давнном CA все вызовы ToTreeNode избыточны. Но это замечаение больше по стилю: интерфейс TreeNode менее стабилен и менее понятен (много синтаксического «мусора»).