当前位置:  开发笔记 > 编程语言 > 正文

C#(.NET)设计缺陷

如何解决《C#(.NET)设计缺陷》经验,为你挑选了21个好方法。

C#或.NET Framework中一些最大的设计缺陷是什么?

示例:没有非可空字符串类型,您必须在从IDataReader获取值时检查DBNull.



1> Marc Gravell..:

Reset()对方法IEnumerator是错误的(对于迭代器块,语言规范,甚至要求,这将引发异常)

在Eric看来,返回数组的反射方法是一个错误

阵列协方差是并且仍然是一个奇怪的; 至少在C#4.0/.NET 4.0中,这是正确完成的IEnumerable

Func 而是失宠了 - 这是一个错误吗?

同步集合 - 一个好主意,但在现实中并不一定有用:你通常需要同步多个操作(List然后ApplicationException),因此同步不同操作的集合并不是那么有用

可以更多地使用Contains/ Addpattern - 也许允许它们共享可重用(可扩展?)语法; 你可以通过返回System.Collections.Concurrent和使用来模拟这个TryAdd,但它本来可以更清楚

迭代器块:没有提前检查参数的简单方法(而不是懒惰).当然,你可以编写两种链式方法,但这很难看

更简单的不变性会很好; C#4.0 有点帮助,但还不够

没有"这个ref-type参数不能为空"支持 - 虽然合同(在4.0中)有点帮助.但语法GetOrAdd(注入空检查/ TryRemove)会很好(与using等对比)

缺乏对具有泛型的运算符和非默认构造函数的支持; C#4.0解决了这个问题lock,或者你可以像这样启用它


迭代器变量在扩展中在while 之外声明IDisposable,这意味着anon-methods/lambdas捕获单个变量,而不是每次迭代一次(使用线程/ async /等时很痛苦)


BCL人员说`ApplicationException`是一个错误 - 没有像他们希望的那样有用.他们还说`System.Exception`应该是`abstract`.
好吧,IEnumerable是一个1.1宿醉; 你可以使用.Cast ()和LINQ,至少
不可为空:将常规引用类型T传递给带有非可空T的东西应该是编译错误!(就像你不能将int?传递给int).当然,通过另一个方向很好.

2> Quibblesome..:

TextWriter是StreamWriter 的类.跆拳道?

这总是让我感到困惑.


+1我每次都要查找.(Whaddaya*意思是*我不能新建TextWriter()?)
StreamWriter这个名称并没有使它写出足够明显的IMO文本.这听起来就像它只是写字节一样,而TextWriter将是一个奇特的实现,它会将(字符串toWrite)的api转换为字节.现在,如果它被称为StreamTextWriter然后肯定,它会立即显而易见但有点长:(

3> Scott Weinst..:

一个小的C#pet peev - 构造函数使用C++/Java语法,使构造函数与类同名.

New()或者ctor()会更好.

当然,像coderush这样的工具使重命名类不再是一个问题,但从可读性POV来看,New()提供了很好的清晰度.


@BlueRaja:Scott指的是类中构造函数的命名.`class Foo {new(int j){i = j} int i; }`

4> naasking..:

我强烈赞同这篇文章(对于那些缺乏ToString的人来说,有一个调试器属性可以为你的类提供自定义格式).

在上面的列表之上,我还会添加以下合理的请求:

    非可空引用类型作为可空值类型的补充,

    允许重写struct的空构造函数,

    允许泛型类型约束来指定密封类,

    我同意这里的另一张海报,当用作约束时请求任意构造函数签名,即.其中T:new(string),或者T:new(string,int),

    我同意另一张关于修复事件的海报,包括空事件列表和并发设置(虽然后者很棘手),

    运算符应该被定义为扩展方法,而不是作为类的静态方法(或者至少不是静态方法),

    允许接口的静态属性和方法(Java有这个,但C#没有),

    允许在对象初始值设定项中初始化事件(目前只允许字段和属性),

    为什么"对象初始化程序"语法仅在创建对象时可用?为什么不随时提供它,即.var e = new Foo(); e {Bar = baz};

    修复二次可枚举行为,

    所有集合都应该具有用于​​迭代的不可变快照(即,变异集合不应该使迭代器无效),

    元组很容易添加,但是像"Either"这样的高效闭合代数类型不是,所以我喜欢某种方式来声明一个封闭的代数类型并在其上强制执行详尽的模式匹配(基本上是对访问者模式的一流支持,但效率更高); 所以只需要使用枚举,使用详尽的模式匹配支持扩展它们,并且不允许无效的情况,

    我一般都喜欢支持模式匹配,但至少对于对象类型测试; 我也有点像这里的另一篇文章中提出的切换语法,

    我同意另一篇文章,System.IO类,如Stream,设计有点差; 任何需要一些实现抛出NotSupportException的接口都是一个糟糕的设计,

    IList应该比它简单得多; 实际上,对于许多具体的集合接口,例如ICollection,这可能是正确的.

    太多的方法抛出异常,例如IDictionary,

    我更喜欢一种比Java更好的检查异常形式(参见类型和效果系统的研究,了解如何做到这一点),

    在通用方法重载决策中修复各种恼人的角点情况; 例如,尝试提供两个重载的扩展方法,一个对引用类型进行操作,另一个对可空的结构类型进行操作,并查看类型推断是如何的,

    提供一种方法来安全地反映INotifyPropertyChanged等接口的字段和成员名称,将字段名称作为字符串; 你可以通过使用带有MemberExpression的lambda的扩展方法来实现这一点,即.()=> Foo,但这不是很有效,

    允许接口中的运算符,并使所有核心数类型实现IArithmetic; 其他有用的共享运算符接口也是可能的,

    变得更难改变对象字段/属性,或者至少允许注释不可变字段并使类型检查器强制执行它(只是将其视为仅限getter的属性fer chrissakes,它并不难!); 事实上,以更合理的方式统一字段和属性,因为两者都没有意义; C#3.0的自动属性是朝着这个方向迈出的第一步,但它们还远远不够,

    简化声明构造函数; 我喜欢F#的方法,但是这里的其他帖子只需要"新"而不是类名,至少更好,

现在我觉得这已足够了.这些都是我在过去一周遇到的烦恼.如果我真的想到它,我可能会继续几个小时.C#4.0已经添加了命名,可选和默认参数,我强烈赞同这些参数.

现在提出一个不合理的要求:

    如果C#/ CLR可以支持类型构造函数多态,那真是非常好.仿制药的仿制药,

好吗?:-)


#3:使用密封类作为泛型类型参数有什么意义,例如Foo 其中T:string?#11:好的,所以我有一个百万Ts的`List `.您如何建议有效地拍摄快照?#21:使用`readonly`关键字....虽然这里有一些很好的建议,但它们大多只是 - 建议,而不是设计缺陷.
这是一个非常有趣的答案,但我认为我们应该使用C#6功能进行更新.示例:项目19和21已实施=)

5> tuinstoel..:

我不明白你做不到

其中T:新(U)

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

编辑:

我想做这个:

public class A 
{
    public A(string text) 
    {

    }
}


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

}


一般来说,缺乏成员限制是令人讨厌的,是的.
阿门,我一直想要这个

6> JoshBerke..:

    我不是Stream,StringWriter,StringReader,TextReader,TextWriter类的忠实粉丝......它只是不直观的是什么.

    IEnumerable.Reset为迭代器抛出异常.我有一些第三方组件,它们总是在数据绑定时调用reset,要求我首先转换为列表来使用它们.

    Xml Serializer应该有序列化的IDictionary元素

    我完全忘记了HttpWebRequest和FTP API我的痛苦....(感谢Nicholas提醒我的评论:-)

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



7> Robert Rossn..:

我真的很惊讶我是第一个提到这个的人:

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,这更令人讨厌.

令人讨厌的是,生成的AddRow方法对于可空类型的概念同样不敏感.由于生成的TableAdapter方法不是这样,所以更是如此.

在.NET中没有太多让我觉得开发团队说"好吧,男孩们,我们足够接近 - 运送它!" 但确实如此.


他们能做的最少的事情就是想出一个新的DataSetV2类(坏名称 - 只是为了参数的缘故),它使用了可空的值类型而不是整个DBNull.

8> BenAlabaster..:

我不知道我会说这是一个设计缺陷,但如果你能用VB中的方式推断一个lambda表达式会非常好:

VB:

Dim a = Function(x) x * (x - 1)

C#

如果能做到这一点会很好:

var a = x => x * (x - 1);

而不是必须这样做:

Func a = x => x * (x - 1);

我意识到它不会长久,但在Code Golf中,每个角色都算死了!他们在设计这些编程语言时不要考虑到这一点吗?:)


微软在设计语言时应该考虑Code Golf吗?
@Ray Burns:在VB中怎么知道?VB支持它,那有什么区别?
@RayBurns类型推断?自1989年以来我一直在使用它.
Lamdas在C#中是Homoiconic.片段`(int x)=> x*(x -1);`可以表示`Func `或者它可以表示`Expression >`
@BenAlabaster:VB支持后期绑定算术运算符.C#必须在编译时解决它们.这是语言差异.例如,VB可以将两个对象一起添加.C#不能,因为`+`没有为`object`定义.

9> dalle..:

    该System.Object的类:

    Equals和GetHashCode - 并非所有类都具有可比性或可散列性,应移至界面.想到IEquatable或IComparable(或类似的).

    ToString - 并非所有类都可以转换为字符串,应该移动到接口.想到了IFormattable(或类似的).

    该ICollection.SyncRoot属性:

    促进设计不佳,外部锁几乎总是更有用.

    仿制药应该从一开始就在那里:

    该System.Collections中命名空间包含了很多或多或少过时的类和接口.



10> Greg Beech..:

令我恼火的事情之一就是Predicate != Func悖论.他们都是类型的代表,T -> bool但他们不兼容任务.



11> ChrisW..:

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


Xenocode的Postbuild没有这样做吗?如果Visual Studio有办法做到这一点会很好......

12> tuinstoel..:

我不喜欢C#switch语句.

我想要这样的东西

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

所以没有更多的休息(容易忘记)以及逗号分隔不同值的可能性.


顺便说一句,我同意OP.`switch`从根本上打破了所有语言,模仿C的deliberatly残缺版本(针对速度优化!).VB的表现要好得多,但与模式匹配(Haskell,F#...)的语言背后仍然相差很多.
一般来说,休息似乎是一个错误,是不是发出它的编译错误?它似乎只是为了缓解从C到C#的过渡(也就是教学开发者,他们不能自动通过)

13> Daniel Paull..:

我们对正确的 OO技术非常了解.解耦,合同编程,避免不正确的继承,适当使用异常,开放/封闭主体,Liskov可替代性等.还有,.Net框架没有采用最佳实践.

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

如果MS关注这一点,那么软件工程世界在这十年中可以在质量,稳定性和可扩展性方面取得巨大飞跃,但唉,它似乎正在倒退.


+1为目标咆哮; 我会补充说,无法对每个类进行子类化,缺少基础类的接口,以及即使在几年后也不愿意修复框架错误
不,我说有很多情况下设计明显存在缺陷,那么当你认为它们做对了,他们仍然会弄错.例如,我在MSDN论坛上的帖子:http://social.msdn.microsoft.com/forums/en-US/wpf/thread/f677b026-0719-47d7-a0b7-a0874df2ba17/
我认为你说的是​​"它没有那么好",这是一个无法回答的问题.没有什么是完美的.提供细节.

14> Thomas Eyde..:

C#中的事件,您必须明确检查侦听器.不是那个事件的重点,向那些碰巧在那里的人广播?即使没有?



15> ShuggyCoUk..:

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

我很清楚,他们知道它,知道如何解决它,但它并没有被视为有足够的优先权值得包容.

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

之美"产量的foreach"是,更简单,更语法正确的鼓励,高性能的代码,这是'成功的坑’,我想他们增加新的功能,为平台的长期成功之前,应该向往.



16> ggf31416..:

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



17> Jay Bazuzi..:

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

当接口成员具有特定于接口的类型的参数(例如, a enum)时,这尤其有用.将enum类型嵌套在接口类型中会很不错.



18> jasonh..:

事件的非常危险的默认性质.由于订阅者被删除,您可以调用事件并处于不一致状态这一事实非常可怕.有关此主题的更多阅读,请参阅Jon Skeet和Eric Lippert的优秀文章.



19> Jon Harrop..:

null 到处.

const 无处.

API是不一致的,例如,变量数组返回void但附加到StringBuffer返回相同的mutable StringBuffer.

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

没有结构类型,所以你写System.Windows.Media.Effects.SamplingMode.Bilinear而不是只是Bilinear.

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

平等和比较是一个烂摊子:你有System.IComparableEquals,但是随后你也有System.IComparable<_>,System.IEquatable,System.Collections.IComparer,System.Collections.IStructuralComparable,System.Collections.IStructuralEquatable,System.Collections.Generic.IComparerSystem.Collections.Generic.IEqualityComparer.

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



20> Michael Buen..:

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

我的建议,好好利用"@"标志:

代替:

if((myVar&MyEnumName.ColorRed)!= 0)

用这个:

if((myVar&MyEnumName.ColorRed)!= @ 0)



21> Brian Rasmus..:

要添加其他人已经提出的长点清单:

DateTime.Now == DateTime.Now 在大多数情况下,但不是所有情况.

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

Monitor.Enter并且Monitor.Exit应该是实例方法,所以不是新建一个特定的对象来锁定,你可以新建一个Monitor并锁定它.

析构函数永远不应该被命名为析构函数.ECMA规范称它们为终结器,这对C++人群来说要少得多,但语言规范仍然将它们称为析构函数.


@Brian Rasmussen:DateTime.Now是一个非常合适的属性,因为它不是通过读取而是通过外部因素来改变.如果一个人读取SomeForm.Width之类的属性,然后 - 在用户调整表单大小后 - 再次读取它,则第二次读取的值将不同.虽然第一个DateTime.Now可能需要足够长的时间才能执行它会影响第二个读取的值,但这样的效果与执行时间相同的任何其他函数没有区别.
"DateTime.Now"一个是世界上最明显的竞争条件,但其余为+1
推荐阅读
郑谊099_448
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有