一般来说,C#或.NET Framework中存在哪些最大的设计缺陷?
示例:没有非可空字符串类型,您必须在从IDataReader获取值时检查DBNull。
答案 0 :(得分:72)
Reset()
上的IEnumerator<T>
方法是错误的(对于迭代器块,语言规范甚至要求这会引发异常)IEnumerable[<T>]
ApplicationException
而不是失宠 - 这是一个错误吗?Contains
,然后Add
),这样一个集合同步不同的操作并不是那么有用using
/ lock
模式 - 也许允许它们共享可重用(可扩展?)语法;您可以通过返回IDisposable
并使用using
来模拟这一点,但它可能更清晰Foo(SqlConnection! connection)
这样的语法(注入空检/ throw
)会很好(与int?
等对比)dynamic
解决了这个问题,或者您可以启用它like this foreach
扩展中声明在之外的迭代器变量,意味着anon-methods / lambdas捕获单个变量,而不是每次迭代一次(线程/异步很痛苦) /等)答案 1 :(得分:60)
TextWriter是StreamWriter的基础类。跆拳道?
总是让我感到困惑。
答案 2 :(得分:42)
一个小的C#pet peev - 构造函数使用C ++ / Java语法,使构造函数与类同名。
New()
或ctor()
本来会更好。
当然,像coderush这样的工具使重命名类不再是一个问题,但从可读性POV来看,New()提供了很好的清晰度。
答案 3 :(得分:39)
我强烈赞同this post(对于那些缺乏ToString的人来说,有一个调试器属性可以为你的班级提供自定义格式。)
在上面的列表之上,我还会添加以下合理的请求:
现在我认为这已足够了。这些都是我在过去一周遇到的烦恼。如果我真的想到它,我可能会继续几个小时。 C#4.0已经添加了命名,可选和默认参数,我强烈赞同。
现在提出一个不合理的请求:
非常好吗? : - )
答案 4 :(得分:29)
我不明白你做不到
其中T:new(U)
因此,您声明泛型类型T具有非默认构造函数。
修改强>
我想这样做:
public class A
{
public A(string text)
{
}
}
public class Gen<T> where T : new(string text)
{
}
答案 5 :(得分:20)
我真的很惊讶我是第一个提到这个的人:
ADO.NET类型化数据集不会将可空列作为可空类型的属性公开。你应该能够写下这个:
int? i = myRec.Field;
myRec.Field = null;
相反,你必须写这个,这只是愚蠢的:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();
这在.NET 2.0中很烦人,而且你现在必须在你漂亮的LINQ查询中使用如上所述的jiggery-pokery,这更令人讨厌。
生成的Add<TableName>Row
方法同样对可空类型的概念不敏感也很烦人。由于生成的TableAdapter
方法不是,所以更是如此。
在.NET中没有太多让我觉得开发团队说“好吧,男孩们,我们足够接近 - 运送它!”但确实如此。
答案 6 :(得分:20)
编辑
5.我的另一个烦恼是System.Reflection.BindingFlags如何根据您使用的方法有不同的用途。在FindFields中,例如CreateInstance或SetField是什么意思?在这种情况下,他们已经超载了这个枚举背后的含义,这令人困惑。
答案 7 :(得分:15)
我不知道我会说它是一个设计缺陷,但是如果你能用VB中的方式推断一个lambda表达式会非常好:
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中,每个角色都算死了!他们在设计这些编程语言时不要考虑到这一点吗? :)
答案 8 :(得分:12)
令我恼火的一件事是Predicate<T> != Func<T, bool>
悖论。他们都是T -> bool
类型的代表,但他们并不是分配兼容的。
答案 9 :(得分:12)
Equals和GetHashCode - 并非所有类都具有可比性或可清除性,应移至界面。想到IEquatable或IComparable(或类似的)。
ToString - 并非所有类都可以转换为字符串,应该移动到接口。想到了IFormattable(或类似的)。
泛型应该从一开始就存在:
答案 10 :(得分:11)
我不喜欢C#switch语句。
我想要这样的东西
switch (a) {
1 : do_something;
2 : do_something_else;
3,4 : do_something_different;
else : do_something_weird;
}
因此不再有中断(容易忘记)以及逗号分隔不同值的可能性。
答案 11 :(得分:11)
有些人(ISV)希望您可以在构建时将其编译为机器代码并链接它,以便创建不需要dotNet运行时的本机可执行文件。
答案 12 :(得分:10)
我们非常了解正确 OO技术。解耦,合同编程,避免不正确的继承,适当使用异常,开放/封闭主体,Liskov可替代性等。但是,.Net框架还没有采用最佳实践。
对我来说,.Net设计中最大的一个缺陷就是没有站在巨人的肩膀上; 向使用其框架的大量程序员推广不太理想的编程范例。
如果MS关注这一点,那么软件工程世界在这十年中可以在质量,稳定性和可扩展性方面取得巨大飞跃,但唉,它似乎正在倒退。
答案 13 :(得分:10)
C#中的事件,您必须明确检查侦听器。不是那个事件的重点,向那些碰巧在那里的人广播?即使没有?
答案 14 :(得分:9)
嵌套/递归iterators的O(N ^ 2)行为可怕(并且对大多数人来说是非常不可见的)。
我很清楚,他们知道这件事,知道how to fix it,但它并没有被视为有足够的优先权值得包容。
我一直在使用树状结构,并且当他们无意中以这种方式引入非常昂贵的操作时,必须纠正智能人员的代码。
“fore foreach”的美妙之处在于更简单,更简单的语法鼓励正确,高效的代码。这是我认为在为平台的长期成功添加新功能之前我们应该追求的"pit of success"。
答案 15 :(得分:7)
接口中的静态成员和嵌套类型。
当接口成员具有特定于接口的类型的参数(例如和enum
)时,这尤其有用。将enum类型嵌套在接口类型中会很好。
答案 16 :(得分:7)
某些类实现接口,但它们没有实现该接口的许多方法,例如,Array实现IList,但9个方法中有4个抛出NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members.aspx
答案 17 :(得分:6)
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
,{{1 },System.Collections.IComparer
,System.Collections.IStructuralComparable
,System.Collections.IStructuralEquatable
和System.Collections.Generic.IComparer
。
元组应该是结构,但结构不必要地禁止尾部调用消除,因此最常见和最基本的数据类型之一将不必要地分配并破坏可扩展的并行性。
答案 18 :(得分:6)
事件非常危险的默认性质。由于订阅者被删除,您可以调用事件并处于不一致状态这一事实非常可怕。有关此主题的更多阅读,请参阅Jon Skeet's和Eric Lippert's优秀文章。
答案 19 :(得分:5)
0兼职作为枚举
枚举的特点:http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx
我的建议,好好利用“@”标志:
而不是:
if((myVar&amp; MyEnumName.ColorRed)!= 0)
使用它:
if((myVar&amp; MyEnumName.ColorRed)!= @ 0)
答案 20 :(得分:5)
添加其他人已经提出的长点清单:
DateTime.Now == DateTime.Now
在大多数情况下,但不是所有情况。
String
这是不可变的,有很多构造和操作选项,但StringBuilder
(可变)却没有。
Monitor.Enter
和Monitor.Exit
应该是实例方法,因此您可以新建一个Monitor
而不是新建一个特定的锁定对象。
析构函数永远不应该被命名为析构函数。 ECMA规范称它们为终结器,这对C ++人群来说要少得多,但语言规范仍然将它们称为析构函数。
答案 21 :(得分:4)
我们使用属性的方式有时会让我感到恼火。我喜欢将它们视为Java的getFoo()和setFoo()方法的等价物。但他们不是。
如果Property Usage Guidelines声明属性应该能够以任何顺序设置,以便序列化可以工作,那么它们对于setter-time验证是无用的。如果您来自某个背景,您希望阻止某个对象进入无效状态,则属性不是您的解决方案。有时我没有看到它们比公共成员更好,因为我们在属性中应该做什么样的事情是如此有限。
为此,我总是希望(这主要是在这里大声思考,我只是希望我可以做这样的事情),我可以以某种方式扩展属性语法。想象一下这样的事情:
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;
}
}
我不确定这是否有用或者它访问的内容是什么样的。但我只是希望我可以用属性做更多的事情,并且真正对待它们就像获取和设置方法一样。
答案 22 :(得分:4)
扩展方法很不错,但它们是一种难以解决的问题,可以用真正的混合解决问题(看看红宝石,看看我在说什么),关于mixins这个问题。将它们添加到语言中的一种非常好的方法是允许将泛型用于继承。这允许您以面向对象的方式扩展现有类:
public class MyMixin<T> : T
{
// etc...
}
这可以像这样用来扩展字符串,例如:
var newMixin = new MyMixin<string>();
它比扩展方法强大得多,因为它允许你覆盖方法,例如包装它们允许在语言中使用类似AOP的功能。
抱歉咆哮: - )
答案 23 :(得分:3)
框架V1中的SqlCommand上的.Parameters.Add()方法设计得非常糟糕 - 如果传入一个值为(int)为0的参数,其中一个重载基本上不起作用 - 这导致他们在SqlCommand类上创建.Parameters.AddWithValue()方法。
答案 24 :(得分:3)
ICollection<T>
和IList<T>
的子集;至少,协变的只读集合接口IListSource<out T>
(带有枚举器,索引器和Count)将非常有用。Transform(Sequence<T>, Func<T,T>)
函数,需要快速确定函数是返回相同的值还是不同的值。如果函数不修改其大部分/全部参数,则输出序列可以共享输入序列中的部分/全部内存。如果没有按比例比较任何值类型T的能力,必须使用更慢的比较,这会极大地损害性能。List<T>
转换为假设的IListSource<U>
(其中T:U),即使该类没有明确地实现该接口。至少有three different libraries(独立编写)来提供此功能(当然,如果有完美的解决方法,则存在性能缺陷,这对于称它为.NET中的一个缺陷。)WeakReference<T>
(您可以轻松编写自己的,但它将在内部使用强制转换。)Predicate<T>
vs Func<T,bool>
)。我经常希望我们可以为接口和委托设置structural typing,以实现组件之间更松散的耦合,因为在.NET中,独立DLL中的类实现相同的接口是不够的 - 它们还必须共享一个公共引用到第三个定义接口的DLL。DBNull.Value
存在,即使null
同样可以达到同样的目的。variable = variable ?? value
。实际上,C#中有一些地方不必要地缺乏对称性。例如,你可以写if (x) y(); else z();
(没有大括号),但你不能写try y(); finally z();
。答案 25 :(得分:3)
能够调用扩展程序 null变量的方法是有争议的 e.g。
对象a = null; a.MyExtMethod(); //这是可调用的,假定它定义了MyExtMethod
它可能很方便但在空引用异常主题上不明确。
一个命名'缺陷'。 System.configuration.dll中“配置”的“C”应大写。
异常处理。应该像Java一样强行捕获或抛出异常,编译器应该在编译时检查它。用户不应该依赖于目标调用中的异常信息的注释。
答案 26 :(得分:3)
Microsoft不会修复框架中的明显错误,也不会提供挂钩,因此最终用户可以修复它们。
此外,没有办法在运行时对.NET可执行文件进行二进制修补,也无法在没有二进制修补本机库(拦截加载调用)的情况下指定.NET框架库的私有版本,而且ILDASM不可再发行无论如何,我无法自动化补丁。
答案 27 :(得分:2)
在1.x中给我带来的一件事是在使用System.Xml.XmlValidatingReader
时,ValidationEventHandler
的{{1}}没有公开基础ValidationEventArgs
(标记为内部) )其中包含XmlSchemaException
和linenumber
等所有有用信息。相反,您应该从Message字符串属性中解析它,或使用反射来挖掘它。当您想要向最终用户返回更消毒的错误时,情况就不那么好了。
答案 28 :(得分:1)
不喜欢它不能在另一个枚举中使用一个枚举的值,例如:
enum Colors { white, blue, green, red, black, yellow }
enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow }
答案 29 :(得分:0)
我老实说,在我多年的.NET(C#)编程期间,我没有在我记得的框架设计中存在缺陷;这意味着在我的情况下可能没有值得记住的缺陷。
然而,在微软发布XNA的几年前,我有些不喜欢的东西,他们完全削减了他们的MDX 2.0版本,这让我的游戏无法播放,也不容易转换。这是一个更广泛的缺陷,与.NET框架无关。
.NET框架实际上遵循了许多高端语言架构开发的很多非常好的设计指南。所以我不得不说我对.NET感到高兴。
但是为了告诉你一些可能更好的东西,我不得不抱怨Generic系统,我没有找到接口的Generics,例如“其中T是MyObj”(这不是完全正确的语法。但是,这部分本来可以做得更好更清晰。
想象一下,有一个2个不同类共享的接口,如果你想在该接口中使用Generic方法,你需要查看一些讨厌的Generics-sytanx。可能只是我想要做奇怪的事情。但对我来说只是一件令人难忘的事。
答案 30 :(得分:0)
StreamWriter和StreamReader类(和子类)在Close()和Dispose()上关闭底层流。无法计算我在这个设计选择中工作了多少次。
答案 31 :(得分:0)
隐式输入的变量在IMO中实施得很差。我知道你在使用Linq表达式时应该只使用它们,但是你不能在局部范围之外声明它们,这很烦人。
来自MSDN:
我认为这是一个糟糕的实现的原因是他们称之为var,但它距离变种还有很长的路要走。它实际上只是简写语法,无需输入完全类名(除了与Linq一起使用时)
答案 32 :(得分:-1)
CLR(以及C#)不支持多重继承,ASP.NET充满了LSP中断......
这些是我的“最爱”......
我可能会发现更多的错误,但那些是我最不喜欢的...... !! :(