C#(.NET)设计缺陷


84

一般而言,C#或.NET Framework中最大的设计缺陷是什么?

示例:没有不可为空的字符串类型,从IDataReader提取值时必须检查DBNull。


这些设计缺陷从什么意义上讲?
朱丽叶

使用IDataReader,您可以使用IsDBNull而不是手动检查
Marc Gravell

9
提示乔恩斯基特谈论密封类;)
johnc

3
使用扩展方法修复IDataReader非常容易:请参见weblogs.asp.net/skillet/archive/2008/06/18/…
罗伯特·罗斯尼

@lagerdalek-如果可以的话,我会对该评论+1;记忆深刻
Marc Gravell

Answers:


39

对此帖子表示强烈同意(对于那些缺乏ToString的人来说,有一个debugger属性可以为您的类提供自定义格式)。

在以上列表的顶部,我还将添加以下合理要求:

  1. 非空引用类型作为对空值类型的补充,
  2. 允许覆盖结构的空构造函数,
  3. 允许通用类型约束指定密封类,
  4. 我同意这里的另一个发帖人的要求,即在用作约束时,即要求任意构造函数签名。哪里T : new(string)哪里T : new(string, int)
  5. 我也同意另一位关于修正事件的发帖人,无论是针对空事件列表还是在并发设置中(尽管后者很棘手)
  6. 运算符应定义为扩展方法,而不应定义为类的静态方法(或至少不仅仅定义为静态方法),
  7. 允许接口使用静态属性和方法(Java有此功能,但C#没有),
  8. 允许对象初始化程序中的事件初始化(当前仅允许使用字段和属性),
  9. 为什么“对象初始化器”语法仅在创建对象时可用?为什么不随时提供它,即。var e = new Foo(); e { Bar = baz };
  10. 修正二次枚举行为
  11. 所有集合都应具有用于迭代的不可变快照(即,对集合进行突变不应使迭代器无效),
  12. 元组很容易添加,但是像“ Either<T>”这样的高效封闭式代数类型却不是,因此我很喜欢一种方法来声明封闭式代数类型并对其进行强制性模式匹配(基本上是对访问者模式的一流支持,但是效率更高);因此,只需使用枚举,并通过详尽的模式匹配支持对其进行扩展,并且不允许出现无效的情况,
  13. 通常,我希望支持模式匹配,但至少要支持对象类型测试。我也很喜欢另一篇文章中提出的switch语法,
  14. 我同意另一篇文章,即System.IO像这样的类Stream在设计上有些差;需要一些实现的任何接口NotSupportedException都是不好的设计,
  15. IList应该比它简单得多;实际上,对于许多具体的收集界面(例如ICollection
  16. 太多的方法引发异常,例如IDictionary
  17. 我宁愿使用一种比Java更好的检查异常形式(有关如何做到这一点,请参见类型和效果系统的研究),
  18. 修复通用方法重载解析中的各种烦人的极端情况;例如,尝试提供两种重载的扩展方法,一种对引用类型进行操作,另一种对可为空的结构类型进行操作,并查看您的类型推断如何,
  19. 提供一种方法来安全地反映接口(如)的字段和成员名,这些接口INotifyPropertyChanged将字段名作为字符串;您可以使用扩展方法,该方法采用带有的lambda MemberExpression,即。() => Foo,但这不是很有效,
    • 更新:C#6.0添加了nameof()用于单个成员名称的运算符,但是它不适用于泛型(nameof(T) == "T"而不是实际的类型参数名称:您仍然需要这样做typeof(T).Name))-也不允许您获取“路径”字符串,例如nameof(this.ComplexProperty.Value) == "Value"限制其可能的应用。
  20. 允许接口中的运算符,并实现所有核心号码类型IArithmetic;其他有用的共享操作员界面也是可能的,
  21. 使对象字段/属性发生变异变得更加困难,或者至少允许对不可变字段进行注释,并使类型检查器强制执行它(只需将其视为仅用于吸气剂的属性,这并不难!);实际上,以一种更明智的方式统一字段和属性,因为没有必要同时拥有两者;C#3.0的自动属性是朝着这个方向迈出的第一步,但还远远不够,
    • 更新:尽管C#具有readonly关键字,并且C#6.0添加了只读自动属性,但是它不像对不可变类型和值的真实语言支持那样严格。
  22. 简化声明构造函数;我喜欢F#的方法,但这里的另一篇文章至少要求“ new”而不是类名更好,

我想现在就足够了。这些都是我过去一周遇到的烦恼。如果我真的下定决心,我可能会继续工作几个小时。C#4.0已经添加了命名,可选和默认参数,我对此表示赞同。

现在,对于一个不合理的请求:

  1. 如果C#/ CLR可以支持类型构造函数多态,那就真的非常好。泛型优于泛型,

漂亮吗?:-)


1
关于#1,每种类型都必须具有一些默认值,或者每当分配了特定类型的变量或字段时,系统都必须提供一种运行构造函数的方法。我更喜欢后者(#2的分支),但是如果可以修饰非虚拟方法/属性以指定应在不进行空检查的情况下调用它们,则可以容纳#1。这将使“字符串”字段之类的行为看起来像它们默认为空字符串而不是null(因为String的静态“长度”函数如果在null字符串上调用可能会返回0)。
supercat

1
关于#2,通常,如果结构不仅可以指定非零填充的构造函数,而且可以指定字节对字节复制的拷贝构造函数,则通常会很有用。实际上,我真的很想看到一个类似.net的框架,该框架可以很好地识别实体可能具有值或引用语义,并允许将值类型堆对象标记为可变,共享不可变或未提交。 (如果未提交的值对象的模式首先是CompareExchange's为可变的,则可能会发生突变;如果它的模式是首先将CompareExchange's为共享,则可能会被共享)。
超级猫

1
优分!对于#1,类型系统注释是常见的解决方案,但我更喜欢通过类型变量(例如T:new())传播构造函数约束。Re:#2,关于复制构造函数的要点,但是我会喜欢上面描述的更通用的构造函数。更好的是完全消除构造函数的区别方法,而仅使它们成为静态方法。这允许使用更简单,更通用的构造模式,尤其是当我们允许接口使用静态方法时。静态方法构造函数+接口中的静态方法也解决了#1问题。
naasking 2011年

3
#3:使用密封类作为泛型类型参数有什么意义,例如Foo <T>其中T:string?#11:好的,所以我List<T>有100万个Ts。您如何建议有效地拍摄快照?#21:使用readonly关键字...。虽然这里有一些不错的建议,但它们大多只是建议-而非设计缺陷。
Qwertie 2012年

2
这是一个非常有趣的答案,但是我认为我们应该使用C#6功能进行更新。示例:项目19和21已实现=)
eduardobr '16

72
  • 上的Reset()方法IEnumerator<T>是错误的(对于迭代器块,语言规范甚至要求这引发异常)
  • 在埃里克看来,返回数组的反射方法是一个错误
  • 数组协方差过去是,现在仍然是奇数
    • 更新:带有.NET 4.0的C#4.0向通用接口(如IEnumerable<out T>Func<in T, out TResult>,但没有具体类型(如List<T>))添加了协变/逆方差支持。
  • ApplicationException 而是失宠了-这是一个错误吗?
  • 同步集合-一个好主意,但实际上不一定有用:您通常需要同步多个操作(Contains,然后是Add),因此同步不同操作的集合并没有那么有用
    • 更新:System.Collections.Concurrent类型的,有TryAddGetOrAddTryRemove,等在.NET Framework 4.0中增加-尽管接受工厂委托方法不能保证工厂将只调用每个键一次做。
  • using/lock模式可能会得到更多使用-也许允许它们共享可重用(可扩展的)语法;您可以通过返回IDisposable并使用using,但可能更清楚了
  • 迭代器块:没有一种简单的提前检查参数的方法(而不是懒惰地)。当然,您可以编写两个链接方法,但这很丑陋
  • 简单的不变性会很好;C#4.0帮助一点,但不是很够
  • 不支持“此ref-type参数不能为null”-尽管合同(在4.0中)对此有所帮助。但是像Foo(SqlConnection! connection)(注入null-check / throw)这样的语法会很好(与int?etc对比)
  • 缺少运算符和泛型的非默认构造函数的支持;C#4.0使用可以解决此问题dynamic,也可以像这样启用它
  • 迭代器变量在扩展中的while之外声明foreach,这意味着匿名方法/ lambda捕获单个变量,而不是每次迭代捕获一个变量(线程/异步/等痛苦)

IEnumerable!= IEnumerable <object>确实很奇怪
Rauhotz

2
好吧,IEnumerable就是1.1宿醉。您至少可以将.Cast <object>()与LINQ一起使用
Marc Gravell

8
BCL人士说这ApplicationException是一个错误-没像他们希望的那样有用。他们还说System.Exception应该这样abstract
杰伊·巴祖兹

2
不可空:将常规引用类型T传递给采用不可空T的对象应该是编译错误!(就像您不能将int传递给int一样)。当然,通过另一个方向也可以。
杰伊·巴祖兹

1
@Jon Harrop:恕我直言,应该已经对不可变数组和只读数组引用提供了支持。可能也适用于其他一些数组变体(例如,“可调整大小的数组”(间接引用),或带有偏移量和界限的数组引用)。
supercat

60

TextWriter是StreamWriter的类。wtf?

这总是使我感到困惑。


19
+1我每次都要查一下。(whaddaya表示我不能新增TextWriter()吗?)
Nicholas Piasecki

1
谢谢上帝...我以为那只是我。
IJ肯尼迪

?它总是写文本的东西,但是只有StreamWriter会向流中写入文本。似乎很简单。
乔恩·汉娜

4
名称StreamWriter并不能使它写的文字足够明显。听起来好像是孤立的,就像只写字节一样,而TextWriter将是最漂亮的实现,它将(string toWrite)的api转换为字节。现在,如果它被称为StreamTextWriter,那么可以肯定,它将立即变得明显,但是有点长:(
Quibblesome 2012年

44

C#小技巧-构造函数使用C ++ / Java语法,使构造函数与类同名。

New()否则ctor()会更好。

可以肯定的是,诸如Coderush之类的工具使得重命名类的问题减少了,但是从可读性的角度来看,New()提供了极大的清晰度。


Erm,它将如何知道您要创建的新实例的内容?
BlueRaja-Danny Pflughoeft,2010年

4
@BlueRaja:Scott指的是类中构造函数的命名。class Foo { new(int j) {i = j} int i; }
dalle 2010年

尽管我100%同意ctor()或Constructor()会更好(不是New--uppercase关键字违反常规),但我还是毫不犹豫地称其为设计缺陷。他们想吸引现有的C ++ / Java开发人员,并且借用许多愚蠢的旧语法约定可以说帮助他们实现了目标。
Qwertie 2012年

与此问题相关的信息:stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist没有办法(仅使用.NET)使用MergeSort,bucketsort或任何其他排序算法对LinkedList进行简单排序,但是使用linq则速度较慢而不是临时实施。
CoffeDeveloper

29

我不明白你做不到

其中T:new(U)

因此,您声明泛型T具有非默认构造函数。

编辑:

我想做这个:

public class A 
{
    public A(string text) 
    {

    }
}


public class Gen<T> where T : new(string text) 
{

}

通用类型声明如何使用该类型的对象,即它们的接口。构造函数是该接口的实现细节,而不是用户所关心的。当您需要创建参数化实例时,请使用工厂。
布赖恩·瓦茨

对于信息,尽管它不能进行编译时检查,但MiscUtil中有一些代码可以有效地使用非默认构造函数(泛型),即无需Activator.CreateInstance或反射。
Marc Gravell

因为它没有意义,并且在某些用途上令人困惑。尽管如此,在处理不可变对象时它还是有用的。
Pop Catalin 09年

7
总的来说,缺少成员限制很烦人,是的。
MichaelGG

3
阿们,我一直想要这个
Steve

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中没有太多内容使我感到开发团队说:“好吧,男孩们,我们已经足够亲密-运送它!” 但这确实可以。


我完全同意!每当我不得不使用它时(经常),这都会使我感到烦恼。啊!+1
艾文(Eyvind)

2
他们至少可以做的是提出一个新的DataSetV2类(坏名称-仅出于参数考虑),该类始终使用可空值类型而不是DBNull。
Christian Hayter,2009年

我们不要忘记要求一个特殊值的荒谬之处DBNull.Value,因为null它本身就足以表示NULL。幸运的是,LINQ-to-SQL仅将null用作NULL。
Qwertie

实际上,荒诞是整个荒诞建筑的构建基石。
罗伯特·罗斯尼

20
  1. 我不是Stream,StringWriter,StringReader,TextReader,TextWriter类的忠实拥护者...这不是很直观的概念。
  2. IEnumerable.Reset引发迭代器异常。我有一些第三方组件,它们在数据绑定时总是调用reset,要求我首先转换为列表才能使用它们。
  3. Xml Serializer应该具有序列化IDictionary元素
  4. 我完全忘记了HttpWebRequest和FTP API,这给我带来了什么痛苦。

编辑
5.我的另一个烦恼是System.Reflection.BindingFlags如何根据您使用的方法具有不同的用途。例如在FindFields中,CreateInstance或SetField是什么意思?在这种情况下,他们已经使此枚举背后的含义过载了,这很令人困惑。


1
+1我必须每次都查找XmlTextWriter,TextWriter等类中的任何一个。与HttpWebRequest / Response内容相同。完全不直观的API。
尼古拉斯·皮亚塞斯基

+ 1-1 = 0:XmlTextWriter等,我同意不可能从名称中真正推断出它们的含义。HttpWebRequest我不同意我觉得它很直观。
AnthonyWJones

我想对自己的每个人。我想使用FTP时,我希望它们能提供更高的抽象水平。
JoshBerke,2009年

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中,每个角色都很重要!他们在设计这些编程语言时没有考虑到这一点吗?:)


3
微软在设计语言时应该考虑Code Golf吗?
jrcs3

3
@Ray Burns:如何在VB中知道?VB支持它,所以有什么区别?
BenAlabaster

3
@RayBurns类型推断?我从1989
。– RD1

3
Lamdas在C#中是同调的。片段(int x) => x * (x -1);可能意味着Func<int, int>或可能意味着Expression<Func<int, int>>
Scott Weinstein

3
@BenAlabaster:VB支持后期绑定算术运算符。C#必须在编译时解决它们。这是语言上的差异。例如,VB可以将两个对象添加在一起。C#不能,因为+没有为定义object
递归

14
  1. System.Object的类:

    • Equals和GetHashCode-并非所有类都是可比较的或可哈希的,应将其移至接口。想到IEquatable或IComparable(或类似版本)。

    • ToString-并非所有类都可以转换为字符串,应将其移至接口。我想到了IFormattable(或类似格式)。

  2. ICollection.SyncRoot属性:

    • 提升设计质量,外部锁几乎总是更有用。
  3. 泛型应该从一开始就存在:


1
1.这些方法太普遍了,以至于奇怪的情况下还可以,有人可以调用Object.Equals在您的班级上有关系吗?已知可能存在或可能没有实现,并且要求:IEquatable,99%类的IFormattable是奇数。
2009年

1
能够使用默认的引用等式,例如以用户定义的对象作为关键字来构造字典,而不必为此目的将代码显式添加到用户定义的对象中,这很有用。我认为Finalize会造成更大的浪费(更好的选择是拥有需要最终实现iFinalizable并明确注册自己以进行最终确定的对象)。OTOH,应该对iDisposable提供更多固有支持,包括如果构造函数引发异常则调用Dispose。
supercat

1
@supercat:只需要EqualityComparer<T>.Default正确更新即可。然后两者var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)var dict = new Dictionary<object, string>()将使用参考比较/平等。
戴尔2010年

1
@supercat:您所描述的正是EqualityComparer<T>.Default它的作用。无需检查每个查找。比较器是Dictionary实例的一个属性,每个实例都Dictionary知道它在使用哪个实例。
dalle 2010年

1
@supercat:词典必须使用最通用的类​​型(即通用基类)作为键,除非使用引用比较,否则除非使用引用比较,否则在同一词典中同时使用Strings和DateTime不会有任何意义。是。请记住,该主题的名称为“ C#(.NET)设计缺陷”。
dalle 2010年

12

令我烦恼的是Predicate<T> != Func<T, bool>悖论。他们都是类型的委托,T -> bool但它们与分配不兼容。


使用Delegate.Create和一些强制转换来进行转换是有技巧的,但是至少能够执行显式强制转换会很好(不过我可以理解缺乏对隐式转换的支持)
Guvante

代表的设计总体上是有缺陷的。例如,缺少弱事件(仅通过一堆Reflection和ReflectionPermission即可完成源端弱事件,而无需订阅者的特殊努力,请参见codeproject.com/Articles/29922/Weak-Events-in-C),效率低下的原因是委托必须是引用类型(委托是更快的,并且在许多情况下,如果它们是值类型,则委托将使用1/3的内存),那么它们将仅仅是一对您可以在堆栈上传递的指针。)
Qwertie 2012年

11

某些人(ISV)希望您可以在构建时将其编译为机器代码并进行链接,以创建不需要dotNet运行时的本机可执行文件。


您应该能够在运行之前对程序进行NGEN。
奥塔维奥·德西奥

与删除运行时的依赖项不同。您只保存在代码上运行的第一个JIT。
Ed S.


难道没有这样做的混淆工具吗?它将框架嵌入到您的exe中,因此您无需部署它。我不能忘记它的名字...这不是PreEmptive的名字
JoshBerke

2
Xenocode的Postbuild不这样做吗?如果Visual Studio有办法做到这一点,那就太好了……
BenAlabaster

11

我们对正确的OO技术了解很多。解耦,按合同编程,避免不正确的继承,异常的适当使用,打开/关闭的主体,Liskov可替换性等等。到目前为止,.Net框架还没有采用最佳实践。

对我来说,.Net设计中最大的缺陷不是站在巨人的肩膀上;而是在互联网上。向使用框架的程序员推广不太理想的编程范例

如果MS对此予以关注,那么在这十年中,软件工程界可能在质量,稳定性和可伸缩性方面取得了巨大飞跃,但是可惜,它似乎正在倒退。


4
+1瞄准目标 我还要补充一点,即无法对每个类进行子类化,缺少基本类的接口以及甚至在几年后仍不愿意修复框架错误
Steven A. Lowe

1
如果我们坚持到底,是不是说.Net框架是如此糟糕,以至于很难确定哪个缺陷最严重?经过一番发泄后,我确实感觉好些了,并感谢我的投票,因为我期望被MS迷们大喊。
Daniel Paull,2009年

我没有投票过,无论如何我都不愿意发表反对投票,但我试图弄清楚为什么我根本不在乎。
Mike Dunlavey,2009年

2
我认为您是在说“这不是它应该做的那样好”,这是没有答案的。没有什么是完美的。提供细节。
jcollum 2009年

3
不,我是说在很多情况下,设计显然存在缺陷,然后当您认为设计正确时,他们仍然会出错。例如,我的岗位MSDN论坛在这里: social.msdn.microsoft.com/forums/en-US/wpf/thread/...
丹尼尔包贵革

11

我不喜欢C#switch语句。

我想要这样的东西

switch (a) {
  1    : do_something;
  2    : do_something_else;
  3,4  : do_something_different;
  else : do_something_weird; 
}

因此,不再有中断(容易忘记)的可能性,并且可以用逗号分隔不同的值。


实际上,我认为如果只需要一个语句或大括号中的一个块,就像C#中的所有其他内容,那会更好。如果没有突破的能力,那么当前的break语法会有些晦涩,并且也不会限制范围。(AFAIK,可能我不知道)
Tamas Czinege,09年

我不明白你的意思。在此处发布您自己的“理想”切换语句。我不想失败,但逗号分隔的值。
tuinstoel

8
顺便说一句,我同意OP。switch从根本上来说,所有模仿C的残废版本(针对速度进行了优化!)的语言都被破坏了。VB的性能要好得多,但与模式匹配的语言(Haskell,F#…)相比仍然落后了数年。
康拉德·鲁道夫

1
tuinostel:类似switch(a){case 1 {do_something; }案例2 {do_something_else; }-也就是说,摆脱break语句,为每种情况要求适当的代码块
Tamas Czinege,2009年

2
通常,中断似乎是一个错误,发出它不是编译错误吗?似乎只有这样才能简化从C到C#的过渡(又名教学开发人员,他们不能自动陷入困境)
Guvante

10

C#中的事件,您必须在其中显式检查侦听器。广播事件的重点不是向碰巧在那里的任何人广播吗?即使没有?


1
令人讨厌的是,当您需要时没有糖,我明白他们为什么要
加倍努力

嗯 我或者不理解或不同意,或者两者都:-)。我不能说我曾经不愿意实例化任何东西。这对我来说就像过早的优化。
Thomas Eyde

在某些情况下,部分方法是可行的选择。
罗伯特·哈维

加上所有导致耦合的原因... MS具有解决这两个问题的CAB,但是由于C#的局限性(例如,字符串而不是枚举作为事件主题),CAB本身也存在许多问题-为什么不做得太松散耦合事件是语言的一部分
BlueRaja-Danny Pflughoeft

9

嵌套/递归迭代器的可怕的(对大多数人来说是不可见的)O(N ^ 2)行为

我很胆怯,他们知道这件事,知道如何解决它,但它不被认为具有应优先考虑的优点。

我一直在使用类似树的结构,当聪明的人无意中以这种方式引入昂贵的操作时,他们必须更正他们的代码。

“ yield foreach”的好处在于,更简单,更容易的语法鼓励正确,高效的代码,这就是“成功之道”。,我想他们增加新的功能,为平台的长期成功之前,应该向往。


这特别有缺陷,因为它违背了O行为的直觉期望,因此会困住很多人
oefe 2009年

7

一些类实现接口,但它们未实现该接口的许多方法,例如Array实现IList,但9种方法中有4种抛出NotSupportedException http://msdn.microsoft.com/zh-cn/library/system.array_members .aspx


好吧,您无法更改数组中元素的数量,因此Add,Clear,Insert和Remove(At)不能执行任何操作,但抛出NotSupported ...实际上,我希望可以为IList返回true的任何IList实现IsFixedSize会抛出它们。
CB

@CB晚会晚了一点:)但是,如果Array无法满足“ IList”,为什么仍然要实现它?这违反了SOLID原则中的L。
温格·森顿

7

接口中的静态成员和嵌套类型。

这是特别有用当一个接口件具有一类型的参数是特定于接口(例如一个enum)。将枚举类型嵌套在接口类型中会很好。


1
这与您的其他建议非常相似吗?
RCIX

1
不,这一个与C#语言有关,另一个与框架有关。并不是每个人都在乎区别,所以让我说这是关于允许的内容,而另一个是关于提供的内容。
杰伊·巴祖兹

6

事件的极其危险的默认性质。由于删除了订阅者,您可以调用事件并处于不一致状态的事实真是太可怕了。有关该主题的更多阅读信息,请参见Jon SkeetEric Lippert的优秀文章。


我不介意默认情况下事件不是线程安全的(它可以提高单线程代码的性能)。愚蠢的是,默认情况下添加/删除是安全的,但是触发事件的自然方式是不安全的,并且没有任何工具可以轻松地使其变得安全。
Qwertie

@Qwertie:愚蠢的是,相当长一段时间以来,添加/删除将使用锁定,但仍然不是线程安全的。
超级猫

6
  • null 到处。

  • const 无处。

  • API是不一致的,例如,对数组return进行变异,void但在return后面追加StringBuffer相同的mutable StringBuffer

  • 集合接口与不可改变的数据结构不兼容,例如,AddSystem.Collections.Generic.IList<_>无法返回结果。

  • 没有结构性的类型,因此您可以编写System.Windows.Media.Effects.SamplingMode.Bilinear而不是Bilinear

  • IEnumerator由类实现的可变接口,当它应该是不可变的时struct

  • 平等和比较是一个烂摊子:你有System.IComparableEquals,但是随后你也有System.IComparable<_>System.IEquatableSystem.Collections.IComparerSystem.Collections.IStructuralComparableSystem.Collections.IStructuralEquatableSystem.Collections.Generic.IComparerSystem.Collections.Generic.IEqualityComparer

  • 元组应该是结构,但是结构不必要地抑制了尾部调用消除,因此最常见和基本的数据类型之一将不必要地分配并破坏可伸缩并行性。


CLR中没有结构化类型,但是您似乎将结构化类型与类型推断或与称为“符号”的Ruby功能混为一谈。如果CLR将Func <int,bool>和Predicate <int>视为相同类型,或者至少是隐式可转换,则为结构化类型。
Qwertie

说到比较,别忘了Comparer <T>!
Qwertie

@Qwertie我指的是编程语言功能,例如OCaml中的多态变体。OCaml的LablGL库提供了许多有趣的结构类型示例,这些结构类型在图形环境中很有用。与类型推断无关,仅与符号切线相关。
JD 2012年

1
一个人如何使用不可变结构IEnumerator
2012年

5

0月光作为枚举

枚举的特性: 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
+1枚举是Java在C#没做对的情况下做的几件事之一
BlueRaja-Danny Pflughoeft 2010年

5

要添加一长串已经由他人提出的优点:

  • DateTime.Now == DateTime.Now 在大多数情况下(并非所有情况下)。

  • String这是不可变的,具有一堆构造和操作选项,但StringBuilder(可变)没有。

  • Monitor.Enter并且Monitor.Exit应该是实例方法,因此您可以新建一个aMonitor并对其进行锁定,而不是为锁定而新建一个特定的对象。

  • 绝对不应将析构函数命名为析构函数。ECMA规范称它们为终结器,对于C ++人群来说,混淆器要少得多,但是语言规范仍将它们称为析构函数。


3
DateTime.Now一个是世界上最明显的竞争条件,但为+1,其余
BlueRaja -丹尼Pflughoeft

与其说它是比赛条件,不如说是他们成为财产的事实。属性看起来就像字段,因此IMO的行为令人惊讶。
布赖恩·拉斯穆森

4
@Brian Rasmussen:DateTime.Now确实是一个属性,因为它不是通过读取而是通过外部因素来更改的。如果读取的是SomeForm.Width之类的属性,然后(在用户调整了窗体的大小之后)一个读取了该属性,则第二次读取的值将有所不同。尽管第一个DateTime.Now可能花费足够长的时间执行,从而影响第二个DateTime读取的值,但这种效果与执行相同时间的任何其他函数没有什么不同。
超级猫

4

我们使用属性的方式有时让我感到恼火。我喜欢将它们视为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方法一样对待它们。


3
我相信它们提供ISerializable接口和隐式构造函数来执行类似的操作,也就是当您不希望序列化程序仅调用属性时。尽管还有更多工作要做,但是看起来您已经使用该方法完成了大部分工作。
古凡特

4

扩展方法虽然不错,但是对于解决本来可以用真正的mixins解决的问题而言,这是一种丑陋的方式(请参阅ruby以了解我在说什么)。将它们添加到语言中的一种非常不错的方法是允许将泛型用于继承。这允许您以一种很好的面向对象的方式扩展现有的类:

public class MyMixin<T> : T
{
    // etc...
}

可以像这样使用它来扩展字符串,例如:

var newMixin = new MyMixin<string>();

它比扩展方法功能强大得多,因为它允许您重写方法,例如将它们包装起来,从而在语言内部提供类似于AOP的功能。

对不起,咆哮:-)


5
有趣,但是我更喜欢扩展方法。如果我得到的库包含一堆字符串扩展方法,则我不想为了获得新的东西而必须将所有对MyMixin <string>的字符串引用进行更改。当然,这是次要的,但是透明添加方法是使扩展方法如此出色的原因。
RCIX

顺便说一句,你知道那已经可行吗?
RCIX

2
我不知道LINQ如何以这种方式工作
BlueRaja-Danny Pflughoeft 2010年

2
@RCIX:Mixins听起来就像我认为扩展方法应该起作用那样。使扩展方法隐式存在的问题是,这意味着实际的类成员需要优先于扩展方法。如果定义了扩展方法Graphics.DrawParallelogram(Pen p,Point v1,Point v2,Point v3),然后将DrawParallelogram函数添加到System.Graphics,该函数使用顺序不同的点,则使用扩展方法的代码将中断而不会警告。顺便说一句,使用两个点作为扩展方法是否存在任何问题(例如object..method()?)
supercat 2010年

3

Microsoft不会修复框架中的明显错误,也不会提供挂钩,以便最终用户可以修复它们。

另外,在运行时无法对.NET可执行文件进行二进制修补,也无法在不对本机库进行二进制修补(以拦截加载调用)的情况下指定.NET Framework库的私有版本,并且ILDASM无法重新分发,因此我无法实现自动化反正补丁。


1
您意味着什么明显的框架错误?
罗伯特·罗斯尼

1
#1单击滚动控件的部分可见的子控件。控件在收到MouseDown事件之前已移入视图,从而导致单击在控件上的位置超出了预期。在树状视图上更糟,它也会触发拖动操作。
约书亚


3
  • 能否在null变量上调用扩展方法是有争议的,例如

    对象a = null; a.MyExtMethod(); //这是可以调用的,假设它已经定义了MyExtMethod

    它可能很方便,但是在空引用异常主题上模棱两可。

  • 一种命名为“缺陷”。System.configuration.dll中“配置”的“ C”应大写。

  • 异常处理。应该像Java中那样强制捕获或引发异常,编译器应在编译时对其进行检查。用户不应依赖注释来获取目标调用中的异常信息。


3
不过非常方便-我有一个“ ThrowIfNull`扩展方法用于参数检查;-p
Marc Gravell

2
你可以这样做?ug ThrowIfNull这是一个有趣的扩展,但这似乎是错误的。
JoshBerke,2009年

1
在普通CLR中,您可以在空引用上调用实例方法,并且如果该方法不访问该对象或其字段,则该调用不会引发空引用异常。(您无法在C#中执行此操作,因为C#即使对于非虚拟方法也使用callvirt)
Pop Catalin 09年

7
例外情况是完全错误的。如果您必须捕获可能在调用堆栈中抛出的所有该死的异常,那么您将无法快速失败。但是我确实希望识别一个特定的调用及其产生的调用

3
@Will:在Java和.net中,异常处理都很麻烦,因为所使用的机制将三个概念紧密地联系在一起,这三个概念有些相关,但也有些正交:(1)哪种类型的事情出错了(数组边界错误, I / O超时等);(2)某些守则是否应采取行动;(3)在什么时候应将问题视为“已解决”。考虑一个例程,该例程应该使用从IEnumerable读取的数据来使对象发生变异。如果该IEnumerable的处理中发生异常,应该怎么办?
超级猫

3

框架V1中SqlCommand上的.Parameters.Add()方法是经过可怕设计的-如果您传递的值(int)为0,则重载之一基本上将不起作用-这导致它们创建SqlCommand类上的.Parameters.AddWithValue()方法。


我同意,但是我认为您的意思是SqlCommand.Parameters.Add()方法。
马特·彼得森

3
  1. ICollection<T>和没有子集IList<T>;至少,协变只读集合接口IListSource<out T>(带有枚举器,索引器和Count)将非常有用。
  2. .NET不支持弱委托。解决方法充其量是笨拙的,在部分信任中,侦听器端的解决方法是不可能的(需要ReflectionPermission)。
  3. 即使合理且不会造成任何问题,也禁止通用接口统一
  4. 与C ++不同,.NET中不允许协变返回类型
  5. 不可能对两种值类型进行按位比较以得出相等性。在功能性的“持久”数据结构中,我正在编写一个Transform(Sequence<T>, Func<T,T>)功能,功能需要快速确定该功能返回的是相同值还是不同值。如果函数未修改其大部分/所有参数,则输出序列可以共享输入序列中的部分/全部内存。如果无法按位比较任何值类型T,则必须使用慢得多的比较,这会严重损害性能。
  6. .NET似乎无法以高性能的方式支持即席界面(如Go或Rust中提供的那些)。即使该类未显式实现该接口,此类接口也将允许您强制List<T>转换为假设IListSource<U>(T:U)。至少有三个 不同的 (独立编写)来提供此功能(当然,在性能方面存在缺陷-如果可能有一个完美的解决方法,则称它为.NET缺陷是不公平的)。
  7. 其他性能问题:IEnumerator每次迭代需要两个接口调用。普通方法指针(IntPtr大小的打开委托)或值类型的委托(IntPtr * 2)是不可能的。固定大小的数组(任意类型T)不能嵌入类中。没有WeakReference<T>(您可以轻松编写自己的脚本,但是它将在内部使用强制类型转换。)
  8. 在某些情况下(例如Predicate<T>vs Func<T,bool>),相同的委托人类型被认为是不兼容的(没有隐式转换),这对我来说是个麻烦。我经常希望我们可以进行结构化打字对接口和委托,以实现组件之间的松散耦合,因为在.NET中,独立DLL中的类不足以实现相同的接口-它们还必须共享对第三个对象的公共引用定义接口的DLL。
  9. DBNull.Value即使null本来可以很好地达到相同的目的,也存在。
  10. C#没有?? =运算符;你必须写variable = variable ?? value。实际上,C#中有一些地方不必要地缺乏对称性。例如,您可以书写if (x) y(); else z();(不带花括号),但不能书写try y(); finally z();
  11. 创建线程时,不可能使子线程从父线程继承线程局部值。BCL不仅不支持此功能,而且除非您手动创建所有线程,否则您无法自己实现它。即使发生线程创建事件,.NET也无法告诉您“父母”或“孩子”给定线程。
  12. 对于不同的数据类型,有两个不同的长度属性,即“长度”和“计数”,这是一个小麻烦。
  13. 关于WPF的糟糕设计,我可以永远继续下去,而且WCF(尽管在某些情况下非常有用)也充斥着疣。通常,许多BCL较新的子库the肿,不直观且文档有限,这使我不愿使用它们。许多新东西本来可以更简单,更小,更易于使用和理解,更松散地耦合,文档更好,适用于更多用例,更快和/或更强类型。
  14. 我经常被属性获取器和设置器之间不必要的耦合所困扰:在派生类或派生接口中,当基类或基本接口只有一个吸气剂时,您不能简单地添加设置器。如果您覆盖了一个getter,那么您将不能定义setter;并且您不能将setter定义为虚拟的,而将getter定义为非虚拟的。

我同意您关于的子集的意见IList<T>,尽管我会使用IReadableByIndex<out T>IAppendable<in T>。我也同意您的许多其他事情。
2012年

这是一个很长的名字。也许我们可以在IListReader<T>;)上妥协-我将“源”一词用作“接收器”(仅写接口)的反义词。
Qwertie 2012年

也许IListSource<in T>还是IReadableList<out T>。使基本接口类型包含并非在所有派生中都存在的方法可能会有价值,尽管我认为使接口有些专门化通常会很好。例如,可能有一个IList<T>,其中包含可能会或可能不会起作用的调整大小的方法,以及一个IResizableList<T>会实现相同方法但保证它们应该起作用的方法。在字段可能包含对可变列表的唯一现存引用或对不可变列表的共享引用的情况下,这种方法可能很有用。
2012年

在这种情况下,想要更改列表内容的代码将检查它是否为可变类型,如果不是,则生成一个新的可变实例,该实例包含与不可变列表相同的项目,然后开始使用它。如果代码每次想要在字段上使用变异方法时都必须不断地对该字段进行类型转换,那将很麻烦。
2012年

@supercat这太令人讨厌了,因为C#没有提供一种真正简单的方法来检查接口是否已实现并立即使用它。MS应该添加语言功能以使其更容易。我的首选技术是绑定表达式if (rl:(list as IResizableList<T>) != null) rl.Add(...);,但还有其他建议。作为各种集合和集合适配器的作者,让我感到烦恼的是编写了大量抛出异常的伪方法。作为类型安全迷,我不想被允许调用非法方法。一个IntelliSense风扇,我不想看到它们列出。
Qwertie 2012年

2

使用时我很生气在1.x的一件事是System.Xml.XmlValidatingReader,在ValidationEventHandlerValidationEventArgs不露出下面的XmlSchemaException(标记内部),其中有像所有有用的信息linenumberposition。相反,您应该从Message字符串属性中解析此内容,或使用反射将其挖掘出来。当您要向最终用户返回更有效的错误时,效果不是很好。


1

不喜欢不能在另一个枚举中使用一个枚举的值,例如:

    enum Colors { white, blue, green, red, black, yellow }

    enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow } 

2
不过,这甚至没有任何意义。 typeof(Color)!= typeof(SpecialColors)
柯克·沃尔

10
这很容易做到:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler

0

隐式类型变量在IMO中的实现效果很差。我知道您实际上只应该在使用Linq表达式时使用它们,但是令人讨厌的是,您不能在本地范围之外声明它们。

从MSDN:

  • var仅可在同一语句中声明和初始化局部变量时使用;该变量不能初始化为null,方法组或匿名函数。
  • var不能在类范围内的字段上使用。
  • 使用var声明的变量不能在初始化表达式中使用。换句话说,此表达式是合法的:int i =(i = 20); 但是此表达式会产生编译时错误:var i =(i = 20);
  • 多个隐式类型的变量不能在同一条语句中初始化。
  • 如果范围内名为var的类型,则var关键字将解析为该类型名称,并且不会被视为隐式类型的局部变量声明的一部分。

我认为这是一个较差的实现,是因为他们将其称为var,但距离变体还有很长的路要走。它实际上只是不需要输入完整类名的简写语法(与Linq一起使用时除外)


肯定是匿名类型(新{...}),而不是隐式类型(var)
马克·格雷夫

只是重新阅读我发布的内容,这是错误的。我的意思是隐式类型变量tho
lomaxx

1
埃里克·利珀特(Eric Lippert)解释了为什么不能在方法之外使用var的原因,这主要是因为它创建了可能性的黑匣子。 blogs.msdn.com/ericlippert/archive/2009/01/26/…–
Guvante,

1
var不打算是变体!正是针对速记(尤其是匿名类型)。享受动态的乐趣……
ShuggyCoUk,2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.