Каковы самые большие недостатки дизайна в C# или .NET Framework в целом?
Пример: не существует строкового типа, не допускающего значения NULL, и вам нужно проверить DBNull при выборке значений из IDataReader.
С IDataReader вы можете использовать IsDBNull вместо проверки вручную
Скажите Джону Скиту, чтобы он поговорил о закрытых классах;)
Исправить IDataReader с помощью метода расширения довольно просто: см. weblogs.asp.net/skillet/archive/2008/06/18/….
@lagerdalek - Я бы добавил +1 к этому комментарию, если бы мог; хорошо помнят
Обычно рекомендуется публиковать свои собственные ответы на вопрос в качестве ответа, а не в самом вопросе, если это не тривиальный пример. (К вашему сведению)
Этот вопрос субъективен и аргументирован. См .: stackoverflow.com/questions/6145786/…
В VS обычно не установлен флажок check for arithmetic overflows. Это может позволить вам забыть об ошибках в случае, если вы имеете дело с большими числами.





Некоторые люди (ISV) хотят, чтобы вы могли скомпилировать его в машинный код во время сборки и связать его, чтобы создать собственный исполняемый файл, которому не нужна среда выполнения dotNet.
Вы должны иметь возможность NGEN для вашей программы перед запуском.
Не то же самое, что удаление зависимостей среды выполнения. Вы сохраняете только первый запуск JIT вашего кода.
См. yoda.arachsys.com/csharp/faq/#framework.required
Разве не существует инструмента для обфускации, который это делает? Он встроит фреймворк в ваш исполняемый файл, поэтому вам не нужно его развертывать. Я не могу вспомнить его название ... это не PreEmptive ...
Разве Postbuild Xenocode не делает этого? Было бы неплохо, если бы у Visual Studio был способ сделать это ...
Ага, а сколько стоит Xenocode? ЕСЛИ какая-то сторонняя компания может его построить, что мешает MS создать его и сделать частью VS?
У Mono есть компоновщик ... не пробовал с .NET: mono-project.com/Linker
Xenocode очень удобен для всех своих возможностей. Около 400 долларов. Думаю, с 30-дневной пробной версией.
как ни странно, Mono делает это! tirania.org/blog/archive/2008/Nov-05.html
Думал можно при установке NGEN? Я могу ошибаться ...
Reset() на IEnumerator<T> был ошибкой (для блоков итератора спецификация языка даже требования, которая вызывает исключение)IEnumerable<out T> и Func<in T, out TResult>, но не для конкретных типов (например, List<T>).ApplicationException попал в немилость - это было ошибкой?Contains, затем Add), поэтому коллекция, которая синхронизирует отдельные операции, не так уж и полезна
using / lock - возможно, позволяя им совместно использовать повторно используемый (расширяемый?) синтаксис; вы можете смоделировать это, вернув IDisposable и используя using, но это могло быть более яснымFoo(SqlConnection! connection) (который вводит нулевую проверку / throw) был бы хорош (в отличие от int? и т. д.)
dynamic, или вы можете включить его как этоforeach, что означает, что анон-методы / лямбда-выражения захватывают единственную переменную, а не одну на итерацию (болезненно с потоковой / асинхронной / и т. д.)
IEnumerable! = IEnumerable <объект> действительно странный
Что ж, IEnumerable - это пережиток версии 1.1; вы можете использовать .Cast <object> () с LINQ, по крайней мере
Ребята из BCL сказали, что ApplicationException был ошибкой - не полезен, как они надеялись. Они также сказали, что System.Exception должен был быть abstract.
Не допускающий значения NULL: передача обычного ссылочного типа T в объект, который принимает не допускающий значение NULL T, должна вызывать ошибку! (точно так же, как вы не можете передать int? в int). Конечно, можно перейти в другую сторону.
ApplicationException: слишком заманчиво использовать существующее исключение во фреймворке, которое точно описывает ошибку в вашем приложении, это слишком похоже на дублирование, чтобы создать новое, производное от ApplicationException.
@AnthonyWJones - действительно. И если он точно описывает ошибку, какой цели он послужит? Затем исходный код должен перехватить FooException системы, FooException app-a и FooException app-b без непрерывности.
FWIW, то, что Эрик Липперт написал в цитированном вами сообщении в блоге, - полная чушь. Нет ничего плохого в возврате массивов. Массивы могут быть неизменяемыми или изменяемыми. Массивы часто идеальны для параллелизма, а чисто функциональные структуры данных - отстой для параллелизма. Настоящий недостаток дизайна, стоящий за проблемой, которую он поднял, заключается в том, что .NET облажался, когда дело дошло до структур данных.
@Jon Harrop: IMHO, должна была быть поддержка неизменяемых массивов и ссылок на массивы только для чтения. Возможно, и для некоторых других вариантов массива (например, «массив с изменяемым размером» (косвенная ссылка) или ссылка на массив со смещением и границей).
@Jon - почему-то я пропустил твой предыдущий комментарий. Скажем так, (и не в первый раз) у нас есть разногласия.
@supercat: Совершенно верно. Что - настоящий недостаток дизайна в .NET.
Насколько я понимаю, IEnumerable.Reset() существует исключительно для того, чтобы соответствовать IEnumVARIANT в COM. Лично я, как человек, ненавидевший его со времен IEnumVARIANT, считаю его присутствие упущенной возможностью.
Метод .Parameters.Add () в SqlCommand в V1 фреймворка был ужасно спроектирован - одна из перегрузок в основном не работала бы, если бы вы передали параметр со значением (int) 0 - это привело к их созданию метод .Parameters.AddWithValue () в классе SqlCommand.
Я согласен, но я думаю, вы имеете в виду метод SqlCommand.Parameters.Add ().
Уметь вызывать расширение метод для нулевой переменной является спорным например
объект a = null; a.MyExtMethod (); // это вызывается, предположим, что где-то он определил MyExtMethod
Это может быть удобно, но это неоднозначно по темам исключений с нулевой ссылкой.
Один «недостаток» в названии. Буква C в слове «configuration» в System.configuration.dll должна начинаться с заглавной буквы.
Обработка исключений. Исключение должно быть принудительно поймано или выброшено, как в Java, компилятор должен проверить его во время компиляции. Пользователи не должны полагаться на комментарии для информации об исключениях в целевом вызове.
Впрочем, очень удобно - у меня есть метод расширения ThrowIfNull для проверки параметров ;-p
ты можешь сделать это? тьфу ThrowIfNull это интересное расширение, но это кажется неправильным.
В простой среде CLR вы можете вызывать методы экземпляра для нулевых ссылок, и если метод не обращается к объекту или его полям, тогда вызов не вызывает исключение нулевой ссылки. (Вы не можете сделать это в C#, поскольку он использует callvirt даже для не виртуальных методов)
Я немного об этом рассказывал в blog.freakcode.com/2008/09/hazards-of-extension-methods.html
Вы можете передавать нулевые значения статическим методам, для которых методы расширения являются просто синтаксическим сахаром. Ваше изменение изменит семантику, особенно в отношении взаимодействия с языками без поддержки методов расширения.
Да, согласен и очень удобно.
исключение совершенно неверно. Вы не можете быстро потерпеть неудачу, если ВЫ ДОЛЖНЫ перехватить каждое чертово исключение, которое может быть выброшено в стек вызовов. Но я ДЕЙСТВИТЕЛЬНО хотел бы, чтобы было намного проще идентифицировать любые исключения, которые могут быть выброшены в конкретном вызове и его результирующем стеке вызовов ...
@ Я искренне согласен, проверенные исключения просто заставляют разработчиков чаще добавлять "throws Exception". И в большинстве реальных случаев это вызывает большую двусмысленность, чем без него.
@Will: Обработка исключений неприятна как в Java, так и в .net, поскольку используемый механизм тесно связывает вместе три концепции, которые в некоторой степени связаны, но также в некоторой степени ортогональны: (1) Какие типы вещей пошли не так (ошибка границ массива, тайм-аут ввода-вывода и т. д.); (2) Должен ли определенный код действовать в результате; (3) В какой момент проблема должна считаться «решенной». Рассмотрим процедуру, которая должна изменять объект данными, считываемыми из IEnumerable. Что должно произойти, если при обработке этого IEnumerable возникнет исключение?
@Will: По сути, у вызывающего абонента есть как минимум два ортогональных вопроса: (1) повлияла ли каким-либо образом оценка IEnumerable на состояние системы и (2) в какой степени неполное поведение мутирующего имеет или не повлиял на мутируемый объект, и мог ли он каким-либо образом повредить состояние системы за пределами этого объекта. Невозможно предоставить оба типа информации обратно вызывающей стороне, если только код, использующий IEnumerable, не имеет средства узнать, какие исключения из него представляют собой внешнее повреждение состояния.
Неявно типизированные переменные были реализованы плохо IMO. Я знаю, что вам действительно следует использовать их только при работе с выражениями Linq, но раздражает то, что вы не можете объявить их за пределами локальной области.
Из MSDN:
Я считаю, что это плохая реализация, потому что они называют это var, но это далеко не вариант. На самом деле это просто сокращенный синтаксис, позволяющий не вводить полное имя класса (кроме случаев, когда он используется с Linq)
Определенно анонимные типы (новые {...}), а не неявно типизированные (var)
Просто перечитайте то, что я опубликовал, и это было неправильно. Я имел в виду неявно типизированные переменные, хотя
Эрик Липперт объяснил, почему var нельзя использовать вне методов, потому что он создает черный ящик возможностей. blogs.msdn.com/ericlippert/archive/2009/01/26/…
var не является вариантом! это как раз для стенографии (особенно для анонимных типов). наслаждайтесь динамикой, когда она появляется ...
Не знаю, могу ли я сказать, что это недостаток дизайна, но было бы здорово, если бы вы могли вывести лямбда-выражение так же, как в VB:
VB:
Dim a = Function(x) x * (x - 1)
C#
Было бы неплохо, если бы можно было сделать так:
var a = x => x * (x - 1);
Вместо того, чтобы делать это:
Func<int, int> a = x => x * (x - 1);
Я понимаю, что это ненадолго, но в Code Golf каждый персонаж чертовски важен! Разве они не принимают это во внимание при разработке этих языков программирования? :)
Microsoft должна учитывать Code Golf при разработке языков?
MS должна учитывать код VB при разработке C# - в VB довольно много «хороших функций», которые отсутствуют в C#!
@gbjbaanb: Проблема в их логике: «Программистам на C# не нужны эти функции VB, потому что они не похожи на все, что они хотели исторически». Поэтому они сохранили все приятные функции для непуристых программистов VB: P
Это шутка? Как компилятор узнает, что вы имели в виду Func <int, int> вместо, например, Func <double, double>?
@ Рэй Бернс: Как это узнать в VB? VB поддерживает это, так в чем разница?
@RayBurns Вывод типа? Я использую это с 1989 года.
Ламды гомоиконны в C#. фрагмент (int x) => x * (x -1); может означать Func<int, int> или может означать Expression<Func<int, int>>
@BenAlabaster: VB поддерживает арифметические операторы с поздним связыванием. C# должен разрешить их во время компиляции. Это разница в языке. Например, VB может складывать два объекта вместе. C# не может, потому что + не определен для object.
Я не понимаю, что ты не можешь сделать
где T: new (U)
Таким образом, вы объявляете, что универсальный тип T имеет конструктор, отличный от конструктора по умолчанию.
редактировать:
Я хочу сделать это:
public class A
{
public A(string text)
{
}
}
public class Gen<T> where T : new(string text)
{
}
Универсальный тип объявляет, как вы будете использовать объекты этого типа, то есть их интерфейс. Конструктор - это деталь реализации этого интерфейса, которая не является заботой потребителя. Когда вам нужно создать параметризованный экземпляр, используйте фабрику.
Для информации, хотя он не может выполнять проверку во время компиляции, в MiscUtil есть некоторый код для эффективного использования конструкторов, отличных от используемых по умолчанию (в дженериках), то есть без Activator.CreateInstance или отражения.
Потому что это не имеет смысла и в некоторых случаях сбивает с толку. Тем не менее, это может быть полезно при работе с неизменяемыми объектами.
Это просто дало мне представление ... Вероятно, вы могли бы добиться этого, используя новую функцию Code Contracts в C# 4.0. Просто добавьте это ограничение в свой конструктор.
В целом, да, отсутствие ограничений на членство раздражает.
аминь, я всегда хотел этого
Для чего бы вы это использовали? Вы же не знаете, что означают параметры, верно?
Править 5. Еще меня беспокоит то, что System.Reflection.BindingFlags по-разному используется в зависимости от используемого вами метода. Например, в FindFields что означает CreateInstance или SetField? Это тот случай, когда они перегрузили смысл этого перечисления, что сбивает с толку.
+1 Мне каждый раз приходится искать любой из классов XmlTextWriter, TextWriter и т. д. То же самое с материалом HttpWebRequest / Response. Совершенно неинтуитивно понятный API.
+ 1-1 = 0: XmlTextWriter и т. д., Я согласен, что невозможно точно вывести, что они из названия. HttpWebRequest Я не согласен, мне это кажется довольно интуитивным.
Полагаю, каждому свое. Я думаю, что с FTP я ожидал бы более высокого уровня абстракции, чем то, что есть у них.
Маленький любимец C# - конструкторы используют синтаксис C++ / Java, при котором имя конструктора совпадает с именем класса.
New() или ctor() были бы намного лучше.
И конечно, такие инструменты, как coderush, делают эту проблему менее сложной для переименования классов, но с точки зрения удобочитаемости New () обеспечивает большую ясность.
Эмм, как он узнает, что вы пытаетесь создать новый экземпляр?
@BlueRaja: Скотт имеет в виду именование конструкторов в классах. class Foo { new(int j) {i = j} int i; }
Хотя я на 100% согласен с тем, что ctor () или constructor () были бы лучше (не New - ключевые слова в верхнем регистре противоречат соглашению), я не решаюсь назвать это недостатком дизайна. Они хотели привлечь существующих разработчиков C++ / Java, и заимствование множества глупых старых синтаксических соглашений, возможно, помогло им достичь своей цели.
связанный с этим вопросом: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedli ул. нет способа (используя только .NET) просто отсортировать LinkedList с помощью MergeSort, bucketsort или любого другого алгоритма сортировки, но с использованием linq, который намного медленнее, чем специальная реализация.
Некоторые классы реализуют интерфейсы, но они не реализуют многие методы этого интерфейса, например, Array реализует IList, но 4 из 9 методов выдают NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members.aspx
Ну, вы не можете изменить количество элементов в массиве, поэтому нет ничего, что может сделать Add, Clear, Insert и Remove (At), кроме как выбросить NotSupported ... Фактически, я ожидал ЛЮБОЙ реализации IList, которая возвращает true для IsFixedSize бросит на них.
@ C.B. немного опоздал на вечеринку, я думаю :) Но если Array не может удовлетворить "IList", зачем вообще его реализовывать? Это нарушение принципа L в SOLID.
Статические члены и вложенные типы в интерфейсах.
Это особенно полезно, когда член интерфейса имеет параметр типа, специфичного для интерфейса (например и enum). Было бы неплохо вложить тип перечисления в тип интерфейса.
Разве это не похоже на другое ваше предложение?
Нет, это о языке C#, а другое о фреймворке. Не всех заботит различие, поэтому позвольте мне сказать, что одно касается того, что разрешено, а другое - того, что предоставляется.
Мы так много знаем о методах объектно-ориентированного программирования верно. Разделение, программирование по контракту, предотвращение неправильного наследования, правильное использование исключений, открытый / закрытый принципал, заменяемость Лискова и т. д. Как бы то ни было, фреймворки .Net не используют передовой опыт.
Для меня самый большой недостаток в дизайне .Net заключается не в том, что он стоит на плечах гигантов; продвижение неидеальных парадигм программирования в массы программистов, использующих их фреймворки.
Если бы MS обратила на это внимание, мир программной инженерии мог бы сделать большие скачки с точки зрения качества, стабильности и масштабируемости в этом десятилетии, но, увы, похоже, что ситуация ухудшается.
+1 за напор о цели; я бы добавил к этому невозможность подкласса каждого класса, отсутствие интерфейсов для фундаментальных классов и нежелание исправлять ошибки фреймворка даже через несколько лет.
Если мы перейдем к делу, говорим ли мы, что фреймворки .Net настолько плохи, что трудно решить, какой из недостатков является наихудшим? Я чувствую себя лучше после выступления и ценю положительный отзыв, так как ожидал, что меня закричат мальчики-фанаты MS.
Я не голосовал в любом случае, и в любом случае я очень не хочу отдавать голоса против, но я пытаюсь понять, почему мне ПРОСТО НЕ ЗАБОТИТЬСЯ.
Я думаю, вы говорите: «Это не так хорошо, как могло бы быть», что не является ответом. Ничто не идеально. Укажите конкретику.
Нет, я говорю, что есть множество случаев, когда дизайн явно ошибочен, но когда вы думаете, что они все делают правильно, они все равно ошибаются. Например, мой пост на форумах MSDN здесь: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
@Will: вы джентльмен и ученый.
Это правда! Я пришел в мир C# из Java и Kotlin. Когда я вижу, как они реализованы здесь в выражениях C# Range и самом типе Range, я буквально хочу спрыгнуть с моста. Вы только посмотрите, как были разработаны типы и интерфейсы Kotlin. Не могу поверить, что в Microsoft не могли придумать такие простые и широко используемые конструкции.
Microsoft не будет исправлять очевидные ошибки во фреймворке и не будет предоставлять хуки, чтобы конечные пользователи могли их исправить.
Кроме того, нет способа двоичного исправления исполняемых файлов .NET во время выполнения и нет способа указать частные версии библиотек .NET framework без двоичного исправления собственных библиотек (для перехвата вызова загрузки), а ILDASM не распространяется, поэтому я не могу автоматизировать в любом случае патч.
Какие очевидные ошибки фреймворка вы имеете в виду?
# 1 Щелкните частично видимый дочерний элемент управления прокручиваемого элемента управления. Элемент управления перемещается в представление до того, как он получит событие MouseDown, в результате чего щелчок будет в другом месте элемента управления, чем ожидалось. Хуже в древовидных представлениях, где также запускается операция перетаскивания.
Честно говоря, я должен сказать, что за годы программирования .NET (C#) у меня не было недостатков в конструкции фреймворка, которые я помнил; Это означает, что в моем случае, вероятно, нет недостатков, о которых стоит помнить.
Однако есть кое-что, что мне не нравилось пару лет назад, когда Microsoft выпускала XNA, они полностью отказались от своей версии MDX 2.0, что сделало мои игры неиграбельными и их нелегко было просто конвертировать. Это более широкий недостаток, не имеющий ничего общего с .NET-фреймворком.
.NET-framework на самом деле следует многим рекомендациям по дизайну Very Good, разработанным многими высокопроизводительными языковыми архитектурами. Так что я должен сказать, что я доволен .NET.
Но чтобы сказать вам кое-что, что могло бы быть лучше, мне пришлось бы пожаловаться на общую систему, я не нахожу универсальных для интерфейсов, таких как «где T - это MyObj» (это не совсем правильный синтаксис. Однако эта часть можно было бы сделать намного лучше и понятнее.
Представьте, что у вас есть интерфейс, который разделяют 2 разных класса, если вам нужен общий метод внутри этого интерфейса, вам нужно пройти через какой-то неприятный Generics-sytanx. Возможно, я просто хочу делать странные вещи. Только памятная вещь для меня.
Иногда меня раздражает то, как мы используем свойства. Мне нравится думать о них как о эквиваленте методов Java getFoo () и setFoo (). Но это не так.
Если в Правила использования собственности указано, что свойства можно устанавливать в любом порядке, чтобы сериализация могла работать, то они бесполезны для проверки во время установки. Если вы пришли из фона, в котором вам нравится не допускать, чтобы объект позволял себе переходить в недопустимое состояние, тогда свойства не являются вашим решением. Иногда мне не удается понять, чем они лучше, чем публичные члены, поскольку мы настолько ограничены в том, что мы предполагаемый должны делать в свойствах.
С этой целью мне всегда хотелось (здесь я в основном размышляю вслух, я просто хотел бы сделать что-то подобное), чтобы я мог как-то расширить синтаксис свойств. Представьте себе что-то вроде этого:
private string password;
public string Password
{
// Called when being set by a deserializer or a persistence
// framework
deserialize
{
// I could put some backward-compat hacks in here. Like
// weak passwords are grandfathered in without blowing up
this.password = value;
}
get
{
if (Thread.CurrentPrincipal.IsInRole("Administrator"))
{
return this.password;
}
else
{
throw new PermissionException();
}
}
set
{
if (MeetsPasswordRequirements(value))
{
throw new BlahException();
}
this.password = value;
}
serialize
{
return this.password;
}
}
Я не уверен, полезно ли это или как будет выглядеть доступ к ним. Но я просто хочу, чтобы я мог делать больше со свойствами и действительно относился к ним как к методам get и set.
Я считаю, что они предоставляют интерфейс ISerializable и неявный конструктор для подобных вещей, например, когда вы не хотите, чтобы сериализатор просто вызывал свойства. Это немного больше работы, но похоже, что вы уже проделали большую часть этой работы с помощью своего метода.
0 подрабатывает enum
особенности enum: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx
как показано на этом хорошем примере: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html
мое предложение, используйте знак "@" с пользой:
вместо:
если ((myVar & MyEnumName.ColorRed)! = 0)
использовать это:
если ((myVar & MyEnumName.ColorRed)! = @ 0)
+1 enum - одна из немногих вещей, которые Java сделала правильно, а C# - нет.
Я действительно удивлен, что первым упомянул об этом:
Типизированные наборы данных ADO.NET не раскрывают столбцы, допускающие значение NULL, как свойства типов, допускающих значение NULL. Вы должны уметь написать это:
int? i = myRec.Field;
myRec.Field = null;
Вместо этого вы должны написать это, что просто глупо:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();
Это раздражало в .NET 2.0, и еще больше раздражает то, что вам приходится использовать jiggery-pokery, как указано выше, в ваших красивых аккуратных запросах LINQ.
Также раздражает то, что сгенерированный метод Add<TableName>Row также нечувствителен к понятию обнуляемых типов. Тем более что сгенерированные методы TableAdapter - нет.
В .NET не так много всего, что заставляет меня чувствовать, что команда разработчиков сказала: «Ладно, ребята, мы достаточно близки - отправьте его!» Но это точно так.
Я полностью согласен! Это меня беспокоит каждый раз, когда мне приходится его использовать (что бывает часто). Ага! +1
Самое меньшее, что они могли сделать, - это придумать новый класс DataSetV2 (плохое имя - просто ради аргумента), который использовал бы типы значений, допускающие значение NULL, вместо DBNull повсюду.
Давайте не будем забывать абсурдность требования специального значения DBNull.Value, когда самого null было бы вполне достаточно для представления NULL. К счастью, LINQ-to-SQL просто использует null вместо NULL.
На самом деле этот абсурд и есть скала, на которой построено все абсурдное здание.
Методы расширения хороши, но это уродливый способ решить проблемы, которые можно было бы решить более чисто с помощью настоящих миксинов (посмотрите на Ruby, чтобы понять, о чем я говорю), касательно миксинов. Действительно хороший способ добавить их в язык - разрешить использование дженериков для наследования. Это позволяет вам расширять существующие классы приятным объектно-ориентированным способом:
public class MyMixin<T> : T
{
// etc...
}
это можно использовать для расширения строки, например:
var newMixin = new MyMixin<string>();
Он намного более мощный, чем методы расширения, потому что позволяет вам переопределять методы, например, оборачивать их, обеспечивая AOP-подобную функциональность внутри языка.
Извините за тираду :-)
Интересно, но я предпочитаю методы расширения. Если я получаю библиотеку, содержащую кучу методов расширения для строк, я не хочу, чтобы мне приходилось менять все строковые ссылки на MyMixin <string>, чтобы получить новый материал. Конечно, это мелочь, но именно прозрачное добавление методов делает методы расширения такими хорошими.
Кстати, знаете ли вы, что это уже работает?
Я не понимаю, как LINQ может работать таким образом
@RCIX: Миксины звучат так, как я думал, должны работать методы расширения. Проблема с созданием неявных методов расширения заключается в том, что это означает, что реальные члены класса должны иметь приоритет над методами расширения. Если определен метод расширения Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3), а затем в System.Graphics добавлена функция DrawParallelogram, которая использует точки в другом порядке, код, использующий метод расширения, сломается без предупреждение. Кстати, возникла бы проблема с использованием двух точек для методов расширения (например, object..method ()?)
Класс System.Object:
Equals и GetHashCode - не все классы сопоставимы или хешируемы, их следует перенести в интерфейс. На ум приходит IEquatable или IComparable (или аналогичный).
ToString - не все классы можно преобразовать в строку, следует переместить в интерфейс. На ум приходит IFormattable (или аналогичный).
Свойство ICollection.SyncRoot:
Дженерики должны были быть там с самого начала:
Что касается ToString (), он вызывается отладчиком для всех объектов, поэтому в любом случае он должен быть в System.Object.
Значит, ToString () должен существовать из-за отладчика? Отладчику не требуется ToString (), он может использовать IFormattable или просто выводить ClassName 0xaddress, если объект не реализует IFormattable.
1. Эти методы настолько распространены, что было решено, что со странностями все в порядке, имеет ли значение, может ли кто-то вызвать Object.Equals в вашем классе? Известно, что реализация может быть или не быть, и требование: IEquatable, IFormattable для 99% классов является нечетным.
2. Чем внешняя блокировка отличается от SyncRoot? Если вы возражаете против оператора naked lock (), вы правы, есть много лучших альтернатив, но он выполняет свою работу. 3. Хорошее замечание
Я согласен с Гуванте: общее правило - всегда делайте общий случай по умолчанию, и классы, в которых полезны .ToString() / .Equals(), - это, безусловно, обычный случай.
Полезно иметь возможность, например, создать словарь с определяемыми пользователем объектами в качестве ключей, используя равенство ссылок по умолчанию, без необходимости явно добавлять код к определяемым пользователем объектам для этой цели. Я бы счел Finalize гораздо большей тратой (лучшей альтернативой было бы иметь объекты, которые потребуют финализации, реализовывать iFinalizable и явно регистрировать себя для завершения). OTOH, должно было быть больше встроенной поддержки iDisposable, включая вызов Dispose, если конструктор выдает исключение.
@supercat: Все, что нужно, - это правильно обновить EqualityComparer<T>.Default. Тогда и var dict = new Dictionary<object, string>(EqualityComparer<object>.Default), и var dict = new Dictionary<object, string>() будут использовать эталонное сравнение / равенство.
@dalle: Проверять при каждом поиске, поддерживает ли ключевой класс для словаря iEquatable, было бы дорого. С другой стороны, система может предоставить статический метод GetEqualityComparer <T> (T thing), который будет возвращать либо «вещь» (если она поддерживает iEquatable), либо метод сравнения ссылок, если нет. Это нужно было бы вызывать только один раз при создании Dictionary, а не один раз для каждого поиска.
@supercat: То, что вы описываете, - это именно то, что делает EqualityComparer<T>.Default. Нет необходимости проверять каждый поиск. Компаратор - это свойство экземпляра Dictionary, и каждый Dictionary знает, какой из них он использует.
@dalle: Если подумать, если решение о том, рассматривать ли ключи как iEquatable, было принято при создании коллекции, это могло иметь странные последствия, если бы подтип iEquatable был добавлен в коллекцию базового типа, отличного от iEquatable (например, наполнение " Строка "s в" Dictionary <Object> "). Можно было бы возразить, что лучше, чтобы все объекты либо использовали ссылочное равенство, либо iEquatable (без явного iEqualityComparator), чем иметь переопределения Equals и GetHashCode для каждого экземпляра, но до того, как дженерики появились на сцене, как можно было бы красиво кодировать для этого?
@supercat: словарь должен использовать наиболее общий тип (то есть общий базовый класс) в качестве ключа, использование как Strings, так и DateTime в одном словаре не будет иметь никакого смысла, если не используется сравнение ссылок, если только определенное пользователем сравнение не доказано, что является. Помните, что название этой темы - «Недостатки дизайна C# (.NET)».
До появления .net 2.0 каждый словарь использовал объект в качестве ключа; никто не мог знать, будут ли объекты поддерживать iEquatable, если только один из них не проверил каждый тип во время выполнения или не имел параметра в конструкторе Dictionary, чтобы сказать об этом. Наличие у каждого объекта поддержки Equals и GetHashCode позволяет избежать этой проблемы. Кроме того, независимо от того, действительно ли это нужно делать, можно добавить несколько очень разных типов ключей в словарь с четко определенной семантикой. Если бы не все типы поддерживали Equals и GetHashCode, это было бы невозможно (к лучшему или к худшему).
@supercat: Вы все еще этого не видите. Dictionary<Key, Value> может содержать ключи только типа Key или типов, производных от Key. Все, что знает словарь, существует в базовом типе Key. Я сказал, что «Дженерики должны были быть там с самого начала». Если бы это было так, это не было бы проблемой.
@dalle: Мы согласны с тем, что отсутствие дженериков стало причиной многих неудачных решений. Однако я бы сказал, что GetHashCode / Equals был доступен только для объектов iEquatable, были бы случаи, когда наилучшее использование коллекции потребовало бы опроса отдельных элементов для iEquatable. Как еще, например, можно было бы поддерживать коллекцию уникальных объектов iDisposable, имея в виду, что структура может реализовывать iDisposable? Может случиться так, что такие случаи могут быть достаточно редкими, чтобы затраты времени выполнения таких сравнений были бы приемлемыми, но это все равно некрасиво.
@dalle: Кстати, как скорость доступа к переопределенному методу базового класса сравнивается со скоростью доступа к интерфейсу? Насколько я понимаю, доступ к методу базового класса немного быстрее. Не большая разница, но тесты на равенство часто выполняются в циклах, которые выполняются много раз.
@supercat: Если вас беспокоит скорость, взгляните на Dictionary<TKey, TValue> с использованием .NET Reflector. Он уже использует интерфейс для всех сравнений, интерфейс IEqualityComparer<TKey>.
TextWriter - это класс база StreamWriter. что за хрень?
Это всегда меня до крайности смущает.
+1 Мне каждый раз приходится это искать. (Whaddaya иметь в виду Я не могу новый TextWriter ()?)
Слава богу ... Я думал, что это только я.
? Всегда что-то записывает текст, но только StreamWriter делает это с потоком. Кажется довольно простым.
Название StreamWriter не делает тот факт, что он пишет текст, достаточно очевидным IMO. Это звучит изолированно, как если бы он просто записывал байты, а TextWriter был бы причудливой реализацией поверх, которая преобразовывала бы для вас api (string toWrite) в байты. Теперь, если бы он назывался StreamTextWriter, тогда, конечно, это было бы сразу очевидно, но немного длинновато :(
Одна из вещей, которая меня раздражает, - это парадокс Predicate<T> != Func<T, bool>. Они оба являются делегатами типа T -> bool, но не совместимы по присваиванию.
Есть трюк с использованием Delegate.Create и некоторого приведения для преобразования, но, по крайней мере, было бы хорошо иметь возможность выполнить явное приведение (однако я могу понять отсутствие поддержки неявного)
Дизайн делегатов в целом ошибочен; например, отсутствие слабых событий (реализация слабых событий на стороне источника без особых усилий со стороны подписчика может быть выполнена только с помощью набора отражений и ReflectionPermission, см. codeproject.com/Articles/29922/Weak-Events-in-C), а также неэффективности, которая проистекает из требования, что делегаты должны быть ссылочными типами ( делегаты были бы быстрее и использовали бы 1/3 памяти во многих случаях, если бы они были типами значений - тогда они были бы просто парой указателей, которые вы могли бы передать в стек.)
Одна вещь, которая помешала мне в 1.x, заключалась в том, что при использовании System.Xml.XmlValidatingReaderValidationEventHandlerValidationEventArgs не раскрывает базовый XmlSchemaException (помеченный как внутренний), который содержит всю полезную информацию, такую как linenumber и position. Вместо этого вы должны проанализировать это из свойства строки сообщения или использовать отражение, чтобы раскопать его. Не очень хорошо, если вы хотите вернуть конечному пользователю более исправленную ошибку.
CLR (и, следовательно, C#) не поддерживает множественное наследование, а ASP.NET забит разрывами LSP ...
Это мои "любимые" ...
Я, наверное, смог бы найти больше ошибок, но это те, которые мне не нравятся больше всего ... !! :(
Множественное наследование в 99,9% случаев обрабатывается интерфейсами, и 99% его использования на языках, которые его поддерживают, являются неуклюжими и запутанными.
@Guvante - согласен. Множественное наследование редко упрощает код или упрощает его понимание и сопровождение. (Действительно, единичное наследование редко бывает хорошей идеей, и все больше людей, похоже, признают, что есть более эффективные способы повторного использования кода.)
@Guvante: Я бы сказал, что 95% времени он обрабатывается с интерфейсами. Однако с методами расширения интерфейсов а также он обрабатывается в 100% случаев. Ура!
множественное наследование полезно и является одной важной отсутствующей функцией языка C#. Однако правильная реализация требует устранения перегрузки метода. Действительно, правильная реализация MI устраняет все предполагаемые недостатки MI и упрощает повторное использование кода через наследование без какого-либо стандартного кода, удовлетворяющего компилятор.
Чтобы добавить к длинному списку хороших моментов, уже отмеченных другими:
DateTime.Now == DateTime.Now в большинстве, но не во всех случаях.
String, который является неизменяемым, имеет множество опций для построения и манипулирования, а StringBuilder (который является изменяемым) - нет.
Monitor.Enter и Monitor.Exit должны были быть методами экземпляра, поэтому вместо создания определенного объекта для блокировки вы можете создать новый Monitor и заблокировать его.
Деструкторы никогда не должны были называться деструкторами. Спецификация ECMA называет их финализаторами, что гораздо меньше сбивает с толку C++, но спецификация языка по-прежнему называет их деструкторами.
DateTime.Now - наиболее очевидное состояние гонки в мире, но +1 для остальных.
Дело не столько в том, что это состояние гонки, сколько в том, что они сделали это свойством. Свойства выглядят точно так же, как поля, поэтому это довольно удивительное поведение IMO.
@Brian Rasmussen: DateTime.Now вполне правильно является свойством, поскольку оно изменяется не при чтении, а под воздействием внешних факторов. Если вы читаете такое свойство, как SomeForm.Width, а затем - после того, как пользователь изменил размер формы - читаете его снова, значение при втором чтении будет другим. Хотя возможно, что выполнение первого DateTime.Now может занять достаточно много времени, чтобы повлиять на значение, считываемое вторым, такой эффект не будет отличаться от любой другой функции, выполнение которой заняло такое же время.
Мне не нравится оператор switch в C#.
Я бы хотел что-то подобное
switch (a) {
1 : do_something;
2 : do_something_else;
3,4 : do_something_different;
else : do_something_weird;
}
Так что больше никаких перерывов (легко забыть) и возможности разделять запятыми разные значения.
На самом деле, я думаю, было бы даже лучше, если бы для этого потребовался либо один оператор, либо блок в фигурных скобках - как и все остальное в C#. Без возможности провалиться текущий синтаксис прерывания будет немного хитроумным, и он также не ограничивает область видимости. (AFAIK, это может быть, я не знаю)
Я не понимаю о чем ты. Опубликуйте здесь свой собственный «идеальный» оператор переключения. Я не хочу проваливаться, но значения, разделенные запятыми.
DrJokepu: провалы существуют даже для непустых предложений. Вам нужно только сделать это явным, используя goto case.
Кстати, я согласен с ОП. switch в корне сломан на всех языках, которые эмулируют намеренно урезанную версию C (оптимизированную для скорости!). VB работает намного лучше, но все еще на световые годы отстает от языков с сопоставлением с образцом (Haskell, F#…).
Конрад: Вы, конечно, правы насчет goto - и, вероятно, это причина того, что до сих пор используется синтаксис переключателя C в C#.
tuinostel: что-то вроде switch (a) {case 1 {do_something; } case 2 {do_something_else; }} - то есть избавление от оператора break а также, требующего правильных блоков кода для каждого случая
В общем, наличие break кажется ошибкой, разве это не ошибка компиляции? Кажется, это только для облегчения перехода с C на C# (он же учит разработчиков, что они не могут автоматически провалиться)
Здесь, здесь! Сделайте общий случай по умолчанию!
События в C#, где вы должны явно проверять слушателей. Разве не в этом была суть событий - транслировать их всем, кто там окажется? Даже если их нет?
Раздражает, что для этого нет сахара, когда вы этого хотите, я понимаю, почему они делают это жестко, это поощряет не создавать экземпляры аргументов события, если они не нужны
Хм. Я либо не понимаю, либо не согласен, либо и то, и другое :-). Я не могу сказать, что когда-либо чувствовал себя обескураженным, создавая что-либо. Для меня это пахнет преждевременной оптимизацией.
В некоторых случаях частичные методы являются жизнеспособной альтернативой.
ПЛЮС все связи, которые это вызывает ... У MS есть CAB, который устраняет обе эти проблемы, но у CAB есть много собственных проблем из-за ограничений в C# (например, строки, а не перечисления как темы событий) - почему бы просто не сделать слабосвязанные события частью языка !?
Ужасное (и совершенно невидимое для большинства) поведение O (N ^ 2) вложенного / рекурсивного итераторы.
Я очень расстроен тем, что они знают об этом, знают как это исправить, но не рассматривается как имеющий достаточный приоритет, чтобы заслуживать включения.
Я все время работаю с древовидными структурами, и мне приходится исправлять код умных людей, когда они непреднамеренно вводят очень дорогостоящие операции таким образом.
Прелесть yield foreach в том, что более простой и легкий синтаксис поощряет правильный, производительный код. Это "яма успеха", к которому, я думаю, им следует стремиться, прежде чем добавлять новые функции для долгосрочного успеха платформы.
Это особенно ошибочно, потому что оно противоречит интуитивным ожиданиям от поведения О и поэтому попадет в ловушку для многих людей.
Не нравится, что вы не можете использовать значения одного перечисления в другом перечислении, например:
enum Colors { white, blue, green, red, black, yellow }
enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow }
Но это даже не имеет смысла. typeof(Color)! = typeof(SpecialColors).
Сделать это достаточно просто: enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Ужасно опасный дефолтный характер событий. Тот факт, что вы можете вызвать событие и оказаться в несогласованном состоянии из-за удаления подписчиков, просто ужасен. См. Отличные статьи Джона Скита и Эрика Липперта для получения дополнительной информации по этой теме.
Я бы не возражал, если бы события по умолчанию не были потокобезопасными (это могло бы повысить производительность в однопоточном коде); что глупо, так это то, что добавление / удаление безопасны по умолчанию, но естественный способ инициирования события небезопасен, и нет возможности легко сделать это безопасным.
@Qwertie: Что еще глупее, так это то, что некоторое время при добавлении / удалении использовалась блокировка, но она все еще не была потокобезопасной.
null везде.
const негде.
API-интерфейсы несовместимы, например изменение массива возвращает void, но добавление к StringBuffer возвращает тот же изменяемый StringBuffer.
Интерфейсы коллекций несовместимы с неизменяемыми структурами данных, например Add в System.Collections.Generic.IList<_> не может вернуть результат.
Нет структурной типизации, поэтому вы пишете System.Windows.Media.Effects.SamplingMode.Bilinear вместо просто Bilinear.
Изменяемый интерфейс IEnumerator, реализуемый классами, когда он должен быть неизменяемым struct.
Равенство и сравнение - это беспорядок: у вас есть System.IComparable и Equals, но есть еще и System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparer и System.Collections.Generic.IEqualityComparer.
Кортежи должны быть структурами, но структуры излишне препятствуют устранению хвостовых вызовов, поэтому один из наиболее распространенных и фундаментальных типов данных будет без необходимости выделять память и разрушать масштабируемый параллелизм.
В CLR нет структурной типизации, но вы, похоже, смешали структурную типизацию с выводом типа или с функцией Ruby, известной как «символы». Структурная типизация была бы, если бы среда CLR считала Func <int, bool> и Predicate <int> одним и тем же типом или, по крайней мере, неявно конвертируемыми.
Говоря о сравнении, не забывайте Comparer <T>!
@Qwertie Я имел в виду такие особенности языка программирования, как полиморфные варианты в OCaml. В библиотеке OCaml LablGL есть много интересных примеров использования структурной типизации в контексте графики. Ничего общего с выводом типа и только косвенно связано с символами.
Как можно использовать неизменяемую структуру IEnumerator?
Классы StreamWriter и StreamReader (и подклассы) закрывают базовый поток в Close () и Dispose (). Не могу сосчитать, сколько раз я работал над этим выбором дизайна.
Правильный дизайн заключался бы в предоставлении вариант для определения того, должен ли StreamReader или StreamWriter взять на себя владение базовым потоком, поскольку в некоторых случаях это должно происходить, а в других - нет.
ICollection<T> и IList<T> нет; как минимум, ковариантный интерфейс сбора только для чтения IListSource<out T> (с перечислителем, индексатором и счетчиком) был бы чрезвычайно полезен.Transform(Sequence<T>, Func<T,T>), которая должна была быстро определить, возвращает ли функция то же значение или другое значение. Если функция не изменяет большую часть / все свои аргументы, тогда выходная последовательность может совместно использовать часть / всю память из входной последовательности. Без возможности побитового сравнения любого типа значения T необходимо использовать гораздо более медленное сравнение, что сильно снижает производительность.List<T> в гипотетический IListSource<U> (где T: U), даже если класс явно не реализует этот интерфейс. Есть по крайней мере триразныебиблиотеки (написано независимо) для обеспечения этой функциональности (конечно, с недостатками производительности - если бы идеальный обходной путь был возможен, было бы несправедливо называть это недостатком в .NET).WeakReference<T> не существует (вы можете легко написать свой собственный, но он будет использовать приведение внутри себя).Predicate<T> против Func<T,bool>). Мне часто хотелось бы иметь структурная типизация для интерфейсов и делегатов, чтобы добиться более слабой связи между компонентами, потому что в .NET недостаточно, чтобы классы в независимых библиотеках DLL реализовали один и тот же интерфейс - они также должны иметь общую ссылку на третью DLL. который определяет интерфейс.DBNull.Value существует, хотя null одинаково хорошо служил бы той же цели.variable = variable ?? value. В самом деле, в C# есть несколько мест, в которых без нужды отсутствует симметрия. Например, вы можете записать if (x) y(); else z(); (без скобок), но не можете записать try y(); finally z();.Я согласен с вами насчет подмножеств IList<T>, хотя я бы использовал IReadableByIndex<out T> и IAppendable<in T>. Многие из ваших других вещей - вопли, с которыми я тоже согласен.
Это действительно длинное имя. Возможно, мы могли бы пойти на компромисс с IListReader<T>;) - Я использую слово «источник» как антоним слова «сток» (интерфейс только для записи).
Может быть, IListSource<in T> или IReadableList<out T>. Может быть полезно, чтобы базовые типы интерфейсов включали методы, которые существуют не во всех производных, хотя я думаю, что часто бывает полезно иметь несколько специализированных интерфейсов. Например, у одного может быть IList<T>, который содержит методы изменения размера, которые могут работать или не работать, и IResizableList<T>, который реализует те же методы, но гарантирует, что они должны работать. Такой подход может быть полезен в случаях, когда поле может содержать либо единственную существующую ссылку на изменяемый список, либо общую ссылку на неизменяемый.
В таком случае код, который хочет изменить содержимое списка, проверит, является ли он изменяемым типом, и, если нет, сгенерирует новый изменяемый экземпляр, содержащий те же элементы, что и неизменяемый список, а затем начнет его использовать. Было бы утомительно, если бы коду приходилось постоянно приводить тип поля каждый раз, когда он хотел бы использовать для него метод изменения.
@supercat Это раздражает только потому, что C# не предоставляет действительно простого способа проверить, реализован ли интерфейс, и немедленно его использовать. MS должна добавить языковую функцию, чтобы упростить задачу. Я предпочитаю использовать связующее выражение if (rl:(list as IResizableList<T>) != null) rl.Add(...);, но есть и другие предложения. Меня как автора различных коллекций и адаптеров коллекций раздражает то, что я пишу множество фиктивных методов, которые генерируют исключения. Как фанат типобезопасности, я не хочу, чтобы мне разрешали вызывать незаконные методы. Поклонник IntelliSense, я не хочу видеть их в списке.
С описанным вами шаблонным сценарием можно довольно легко справиться без новой языковой конструкции: {var rl=list as IResizeableList<T>; if (rl!=null)...; проблема в том, что это требует бокса. Более интересным был бы способ сказать: «Вызвать метод с параметрами с общими ограничениями для универсального объекта, если это возможно, или выполнить какое-либо другое действие, если нет». Я думаю, что компилятор мог бы сделать это без изменений во время выполнения, если бы он создавал общий статический класс для каждой такой ситуации. Например (с использованием удобочитаемых имен) MaybeDisposer<T> с общедоступным статическим методом Dispose(ref T item) ...
... который вызовет делегата DisposeIt. Первоначально этот делегат будет настроен на подпрограмму, которая будет проверять, реализует ли T (параметр универсального типа класса) IDisposable; в противном случае он установил бы для делегата режим бездействия; в противном случае он использовал бы Reflection для создания делегата для вызова подпрограммы с параметром, ограниченным до IDisposable. Стоимость вызова такой процедуры после первого использования с любым конкретным типом будет равна стоимости одного вызова через статический делегат. Никаких изменений во время выполнения .net не потребуется.
Решением проблемы «надоедливых фиктивных методов» было бы позволить интерфейсу IFoo обозначать статический класс IFooHelpers, так что если .NET загружает класс, который утверждает, что реализует IFoo, но не имеет int fnord(string st), среда выполнения автоматически генерирует int IFoo.fnord(string st) { return IFooHelpers.ClassHelpers.fnord(this, st); }. Добавьте средство для интерфейса, чтобы указать строгие имена других интерфейсов, с которыми он должен считаться совместимым, и многие давние проблемы могут быть легко решены.
В каком смысле эти недостатки дизайна?