Каковы самые большие недостатки дизайна в C # или .NET Framework в целом?
Пример: не существует строкового типа, не допускающего значения NULL, и вам нужно проверить DBNull при выборке значений из IDataReader.
Каковы самые большие недостатки дизайна в C # или .NET Framework в целом?
Пример: не существует строкового типа, не допускающего значения NULL, и вам нужно проверить DBNull при выборке значений из IDataReader.
Ответы:
Я категорически согласен с этим сообщением (для тех, кто жалуется на отсутствие ToString, есть атрибут отладчика, обеспечивающий настраиваемый формат для вашего класса).
В верхней части приведенного выше списка я бы также добавил следующие разумные запросы:
T : new(string) или гдеT : new(string, int)var e = new Foo(); e { Bar = baz };Either<T> " - нет, поэтому мне бы хотелось каким-нибудь способом объявить закрытый алгебраический тип и обеспечить для него исчерпывающее сопоставление с образцом (в основном первоклассная поддержка шаблона посетителя, но намного эффективнее); так что просто возьмите перечисления, расширьте их исчерпывающей поддержкой сопоставления с образцом и не допускайте недопустимых случаев,System.IOклассы вроде бы Streamплохо спроектированы; любой интерфейс, требующий некоторых реализаций, NotSupportedExceptionявляется плохим дизайном,IListдолжно быть намного проще, чем есть; фактически, это может быть верно для многих конкретных интерфейсов коллекций, например ICollection,INotifyPropertyChanged, которые принимают имя поля в виде строки; вы можете сделать это, используя метод расширения, который принимает лямбду с a MemberExpression, т.е. () => Foo, но это не очень эффективно,
nameof()оператор для имен с одним членом, но он не работает в универсальных шаблонах ( nameof(T) == "T"вместо фактического имени аргумента типа: вам все равно нужно это сделать typeof(T).Name)) - и при этом он не позволяет вам получить строку «путь» , например, nameof(this.ComplexProperty.Value) == "Value"ограничение его возможных приложений.IArithmetic ; также возможны другие полезные общие интерфейсы оператора,readonlyключевое слово, а в C # 6.0 добавлены автоматические свойства только для чтения, хотя это не так строго, как настоящая языковая поддержка неизменяемых типов и значений.Полагаю, пока этого достаточно. Это все раздражения, с которыми я столкнулся на прошлой неделе. Я, вероятно, мог бы продолжать в течение нескольких часов, если бы действительно сосредоточился на этом. C # 4.0 уже добавляет именованные, необязательные аргументы и аргументы по умолчанию, что я категорически одобряю.
Теперь по одной необоснованной просьбе:
Довольно, пожалуйста? :-)
List<T>миллион ТС. Как вы предлагаете сделать снимок эффективно? №21: Используйте readonlyключевое слово .... хотя здесь есть несколько хороших предложений, в основном они всего лишь предложения, а не недостатки дизайна.
Reset()метод на IEnumerator<T>была ошибка (для блоков итераторов, язык SPEC даже требования , что это бросает исключение)IEnumerable<out T>и Func<in T, out TResult>, но не конкретных типов (например List<T>).ApplicationException скорее впал в немилость - это было ошибкой?Contains, тогдаAdd ), поэтому коллекция, которая синхронизирует отдельные операции, не так уж и полезна
System.Collections.Concurrentтипы , с TryAdd, GetOrAdd, TryRemoveи т.д. были добавлены в .NET Framework 4.0 - хотя методы , которые принимают заводскую делегат не гарантируют завод будет запущен только один раз за ключ.using/ lock- возможно, позволяя им совместно использовать повторно используемый (расширяемый?) синтаксис; вы можете смоделировать это, вернувшись IDisposableи используя using, но это могло быть более понятнымFoo(SqlConnection! connection)(который вводит нулевую проверку / throw) был бы хорош (в отличие отint? т. Д.)
dynamic, или вы можете включить его такforeach расширении, что означает, что анон-методы / лямбда-выражения захватывают единственную переменную, а не одну на итерацию (болезненно с потоковой / асинхронной / и т. д.)
ApplicationExceptionбыла ошибка - не полезная, как они надеялись. Они также сказали, что так System.Exceptionдолжно было быть abstract.
TextWriter - это базовый класс StreamWriter. что за?
Это всегда меня до крайности смущает.
Маленький любимец C # - конструкторы используют синтаксис C ++ / Java, в котором имя конструктора совпадает с именем класса.
New() или же ctor() было бы намного лучше.
И конечно, такие инструменты, как coderush, делают эту проблему менее сложной для переименования классов, но с точки зрения удобочитаемости New () обеспечивает большую ясность.
class Foo { new(int j) {i = j} int i; }
Newключевые слова --uppercase не противоречат соглашению), я не решаюсь назвать это недостатком дизайна. Они хотели привлечь существующих разработчиков C ++ / Java, и заимствование множества глупых старых синтаксических соглашений, возможно, помогло им достичь своей цели.
Я не понимаю, что ты не можешь
где T: new (U)
Таким образом, вы объявляете, что универсальный тип T имеет конструктор, отличный от конструктора по умолчанию.
редактировать:
Я хочу сделать это:
public class A
{
public A(string text)
{
}
}
public class Gen<T> where T : new(string text)
{
}
Я очень удивлен, что первым упомянул об этом:
Типизированные наборы данных 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 не так много всего, что заставляет меня чувствовать, что команда разработчиков сказала: «Хорошо, ребята, мы достаточно близки - отправьте его!» Но это точно так.
DBNull.Value, когда nullсамо по себе было бы вполне достаточно для представления NULL. К счастью, LINQ-to-SQL просто использует null вместо NULL.
редактировать
5. Еще одно мое раздражение - это то, что System.Reflection.BindingFlags может использоваться по-разному в зависимости от используемого вами метода. Например, в FindFields что означает CreateInstance или SetField? Это тот случай, когда они перегрузили смысл этого перечисления, что сбивает с толку.
Я не знаю, могу ли я сказать, что это недостаток дизайна, но было бы очень хорошо, если бы вы могли вывести лямбда-выражение так же, как в 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 каждый персонаж чертовски важен! Разве они не принимают это во внимание при разработке этих языков программирования? :)
(int x) => x * (x -1);может означать Func<int, int>или может означатьExpression<Func<int, int>>
+не определен для object.
Класс System.Object :
Equals и GetHashCode - не все классы сопоставимы или хешируемы, следует перенести в интерфейс. На ум приходит IEquatable или IComparable (или аналогичный).
ToString - не все классы можно преобразовать в строку, их следует переместить в интерфейс. На ум приходит IFormattable (или аналогичный).
Свойство ICollection.SyncRoot :
Дженерики должны были быть там с самого начала:
EqualityComparer<T>.Defaultправильно обновить . Тогда оба var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)и var dict = new Dictionary<object, string>()будут использовать сравнение / равенство ссылок.
EqualityComparer<T>.Defaultделает. Нет необходимости проверять каждый поиск. Компаратор - это свойство Dictionaryэкземпляра, и каждый Dictionaryзнает, какое из них он использует.
Одна из вещей, которая меня раздражает, - это Predicate<T> != Func<T, bool>парадокс. Они оба являются делегатами типа, T -> boolно не совместимы по присваиванию.
Некоторые люди (ISV) хотят, чтобы вы могли скомпилировать его в машинный код во время сборки и связать его, чтобы создать собственный исполняемый файл, которому не нужна среда выполнения dotNet.
Мы так много знаем о праве методах объектно-ориентированного программирования. Разделение, программирование по контракту, избежание неправильного наследования, правильное использование исключений, открытый / закрытый принципал, заменяемость Лискова и т. Д. Как бы то ни было, в .Net-фреймворках не используются лучшие практики.
На мой взгляд, самый большой недостаток в дизайне .Net заключается не в том, что он стоит на плечах гигантов; продвижение неидеальных парадигм программирования в массы программистов, использующих их фреймворки .
Если бы MS обратила на это внимание, мир программной инженерии мог бы сделать большие скачки с точки зрения качества, стабильности и масштабируемости в этом десятилетии, но, увы, похоже, что он идет на спад.
Мне не нравится оператор switch в C #.
Я бы хотел что-то подобное
switch (a) {
1 : do_something;
2 : do_something_else;
3,4 : do_something_different;
else : do_something_weird;
}
Так что больше никаких разрывов (легко забыть) и возможности разделять запятыми разные значения.
switchфундаментально нарушен на всех языках, которые эмулируют намеренно урезанную версию C (оптимизированную для скорости!). VB работает намного лучше, но все еще на световые годы отстает от языков с сопоставлением с образцом (Haskell, F #…).
События в C #, где вы должны явно проверять слушателей. Разве не в этом был смысл событий транслировать для всех, кто там окажется? Даже если их нет?
Ужасное (и совершенно незаметное для большинства) поведение вложенных / рекурсивных итераторов O (N ^ 2) .
Я очень расстроен, что они знают об этом, знают, как это исправить но это не рассматривается как имеющее достаточный приоритет, чтобы заслужить включение.
Я все время работаю с древовидными структурами, и мне приходится исправлять код умных людей, когда они непреднамеренно вводят очень дорогостоящие операции таким образом.
Прелесть yield foreach в том, что более простой и легкий синтаксис поощряет правильный, производительный код. Это «яма успеха», к которой, я думаю, им следует стремиться, прежде чем добавлять новые функции для долгосрочного успеха платформы.
Некоторые классы реализуют интерфейсы, но они не реализуют многие методы этого интерфейса, например, Array реализует IList, но 4 из 9 методов выдают NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx
Статические члены и вложенные типы в интерфейсах.
Это особенно полезно, когда член интерфейса имеет параметр типа, специфичного для интерфейса ( например , enum). Было бы неплохо вложить тип enum в тип интерфейса.
Ужасно опасный дефолтный характер событий. Тот факт, что вы можете вызвать событие и оказаться в несогласованном состоянии из-за удаления подписчиков, просто ужасен. См. Отличные статьи Джона Скита и Эрика Липперта для получения дополнительной информации по этой теме.
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.
Кортежи должны быть структурами, но структуры излишне препятствуют исключению хвостовых вызовов, поэтому один из наиболее распространенных и фундаментальных типов данных будет без необходимости выделять память и разрушать масштабируемый параллелизм.
IEnumerator?
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)
Чтобы добавить к длинному списку хороших моментов, уже отмеченных другими:
DateTime.Now == DateTime.Now в большинстве, но не во всех случаях.
Stringкоторый является неизменным, имеет множество опций для построения и манипуляции, но StringBuilder(который является изменяемым) - нет.
Monitor.Enterи Monitor.Exitдолжны были быть методами экземпляра, поэтому вместо создания определенного объекта для блокировки вы можете создать новый a Monitorи заблокировать его.
Деструкторы никогда не должны были называться деструкторами. Спецификация ECMA называет их финализаторами, что гораздо меньше сбивает с толку C ++, но спецификация языка по-прежнему называет их деструкторами.
DateTime.NowОдин в мире наиболее очевидная гонка условие, но +1 для остального
Иногда меня раздражает то, как мы используем свойства. Мне нравится думать о них как о эквиваленте методов 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.
Методы расширения хороши, но это уродливый способ решить проблемы, которые можно было бы решить более чисто с помощью настоящих миксинов (посмотрите на Ruby, чтобы понять, о чем я говорю) в отношении миксинов. Действительно хороший способ добавить их в язык - разрешить использование дженериков для наследования. Это позволяет вам расширять существующие классы приятным объектно-ориентированным способом:
public class MyMixin<T> : T
{
// etc...
}
это можно использовать, например, для расширения строки:
var newMixin = new MyMixin<string>();
Он намного более мощный, чем методы расширения, потому что позволяет вам переопределять методы, например, оборачивать их, обеспечивая AOP-подобные функции внутри языка.
Извините за тираду :-)
Microsoft не будет исправлять очевидные ошибки во фреймворке и не будет предоставлять крючки, чтобы конечные пользователи могли их исправить.
Кроме того, нет способа двоичного исправления исполняемых файлов .NET во время выполнения и нет способа указать частные версии библиотек .NET framework без двоичного исправления собственных библиотек (для перехвата вызова загрузки), а ILDASM не распространяется, поэтому я не могу автоматизировать в любом случае патч.
Возможность вызвать метод расширения для нулевой переменной спорна, например
объект a = null; a.MyExtMethod (); // это вызывается, предположим, что где-то он определил MyExtMethod
Это может быть удобно, но это неоднозначно по темам исключений с нулевой ссылкой.
Один недостаток в названии. Буква «C» в слове «конфигурация» в System.configuration.dll должна начинаться с заглавной буквы.
Обработка исключений. Исключение должно быть принудительно поймано или выброшено, как в Java, компилятор должен проверять его во время компиляции. Пользователи не должны полагаться на комментарии для информации об исключениях в целевом вызове.
Метод .Parameters.Add () в SqlCommand в V1 фреймворка был ужасно спроектирован - одна из перегрузок в основном не работала бы, если бы вы передали параметр со значением (int) 0 - это привело к их созданию метод .Parameters.AddWithValue () в классе SqlCommand.
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>vs 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>который реализует те же методы, но гарантирует, что они должны работать. Такой подход может быть полезен в тех случаях, когда поле может содержать единственную существующую ссылку на изменяемый список или общую ссылку на неизменяемый.
if (rl:(list as IResizableList<T>) != null) rl.Add(...);, но есть и другие предложения. Меня как автора различных коллекций и адаптеров коллекций раздражает написание множества фиктивных методов, которые генерируют исключения. Как поклонник типобезопасности, я не хочу, чтобы мне разрешали вызывать незаконные методы. Поклонник IntelliSense, я не хочу видеть их в списке.
Одна вещь , которая галочкой меня в 1.x было при использовании System.Xml.XmlValidatingReader, то ValidationEventHandler«s ValidationEventArgsне выставляет Базового XmlSchemaException(помечено внутренний) , который имеет всю полезную информацию , как linenumberи position. Вместо этого вы должны проанализировать это из свойства строки сообщения или использовать отражение, чтобы раскопать его. Не очень хорошо, если вы хотите вернуть конечному пользователю более исправленную ошибку.
Не нравится, что вы не можете использовать значения одного перечисления в другом перечислении, например:
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 }
Неявно типизированные переменные были реализованы плохо IMO. Я знаю, что вам действительно следует использовать их только при работе с выражениями Linq, но раздражает, что вы не можете объявить их вне локальной области.
Из MSDN:
Я считаю, что это плохая реализация, потому что они называют это var, но это далеко не вариант. На самом деле это просто сокращенный синтаксис, позволяющий не вводить полное имя класса (кроме случаев, когда он используется с Linq)