在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
文中充满了各种C#与其他语言的对比及吐槽, 希望介意者勿观… 当然, 鉴于太乱, 我怀疑有没有人能看完. C#也的确不是当年那种微软, Windows独占的语言了, mono项目已经将C#移植到了Mac和Linux上, 甚至还包括iOS和Android. 也就是说, 假如你愿意的话, 你可以使用C#通吃所有平台, 当然, 前提是你能接受巨贵的授权费用. 作为什么事情都喜欢自己搞一套的微软,(因为他都是垄断的) 在C#这件事情上一开始就很开放(因为有JAVA在前), 总算是做对了一件事情. Hello WorldMono在创建一个名为test的console的工程后, 给了我一个Hello World的代码, using System; namespace test { class MainClass { public static void Main (string[] args) { Console.WriteLine ("Hello World!"); } } } 第一眼看去, 就知道C#中了JAVA的毒了, 用所谓完全面向对象的方式, 强迫你写一堆臃肿而无用的代码… 我至今也没有明白为什么Main函数最后会变成Main类, 也没有明白这有什么好处… 想起最近看的一篇文章Why Java Sucks and C# Rocks, 说”自从C# 1.0诞生之日起,就只出现Java借鉴C#特性的情况”, 以此来驳斥JAVA一派对C#抄袭的指责. 其实, 光是从Hello World都能看出来C#对JAVA的模仿, 他的言论也的确是避重就轻了, 因为, 连他也无法否认C#诞生前及诞生过程中发生的事情… 值得一提的是, C#对传统的 变量与表达式
动态类型大家都知道类似C/C++, JAVA, C#这种对效率还稍微有些追求的语言都是静态类型语言, 并且靠编译期静态类型检查来排查错误, 而从C++以后, 各语言都以更加’真正的’强类型自豪. 而类似 public static dynamic Add(dynamic var1, dynamic var2) { return var1 + var2; } 类似上面的 值类型和引用类型当我看到Why Java Sucks and C# Rocks(2):基础类型与面向对象一文时, 我还以为C#真的已经是所谓的”完全面向对象”了. 所以当我看到C#入门经典书中写到变量的类型还是分为引用类型和值类型时, 我相当意外.(我先看的那一系列文章, 再看的C#入门经典) 等我看到书中写到封箱和拆箱的时候, 就更加惊讶了… 都是一个对象, 为啥还要封箱和拆箱呢? 当然, 鉴于Objective-C连自动的封箱和拆箱都还没有, 我也不能说这就有多么落后. checked支持的受限强制转换增加了checked, unchecked(默认)关键字来应付类型转换时的溢出问题. 比如下面的代码: short source = 257; byte dest = (byte)source; 上面的代码在转换时会发生溢出, 这往往不是我们要的结果, 也往往因此出现莫名而难以调试的bug. 而类似下面的代码会在运行时会抛出 流程控制
数组
函数
可选参数实际上就等于C++里面的参数默认值, 在有默认值时, 在函数最后的参数为可选. 看书中说C#是在C#4后才支持, 为啥呢? 命名参数命名参数在动态语言里面是很常见的, 并且是个容易理解又很使用的功能. 但是C++并没有支持, 看BS在C++语言的设计与演化中讲到其实当时有过讨论, 只是因为在C++中有所谓的接口与实现分离, 而看以前的代码, 很多接口(头文件)使用的参数名和实现中用的参数名并不一样, 所以这个有用的特性并没有加入C++. 这也是为什么我前面说接口与实现分离实在是太不DRY的一个原因. HWND WINAPI CreateWindow( _In_opt_ LPCTSTR lpClassName, _In_opt_ LPCTSTR lpWindowName, _In_ DWORD dwStyle, _In_ int x, _In_ int y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam ); 事实上, 在Win32 API里面, 你要完整的创建一个窗口, 还有类似注册窗口类等巨多参数的接口, 而其实在这个API里面, 每次调用时真正需要使用的又并不是所有的参数, 不用说有多不方便了. 可选参数(参数默认值), 只能在参数列表的最后使用, 让这种简化有的时候变成了一个排序游戏, 到底哪个参数才是最不常用的呢? public static int CreateWindow ( int lpClassName = 0, int lpWindowName = 0, int dwStyle = 0, int x = 0, int y = 0, int nWidth = 0, int nHeight = 0, int hWndParent = 0, int hMenu = 0, int hInstance = 0, int lpParam = 0 ) { return 0; } public static void Main (string[] args) { CreateWindow(nWidth: 640, nHeight: 960); } 还有比这更方便的事情吗? 顺面吐槽一句, Objective-C里面函数调用的方式简直就是为命名参数准备的, 当然竟然完全不支持命名参数, 甚至不支持参数默认值, 崩溃啊… 委托(delegate)这算是接触到的第一个较新的概念, 多写一点. C#的委托: using System; namespace test { class MainClass { delegate int Actor (int leftParam, int rightParam); static int Call (Actor fun, int leftParam, int rightParam) { return fun(leftParam, rightParam); } static int Multiply (int leftParam, int rightParam) { return leftParam * rightParam; } static int Divide (int leftParam, int rightParam) { return leftParam / rightParam; } public static void Main (string[] args) { Console.WriteLine ("{0}", Call(new Actor(Multiply), 10, 10)); Console.WriteLine ("{0}", Call(new Actor(Divide), 10, 10)); Console.ReadKey(); } } } 匿名函数(Lambda)有匿名函数的语言才算是现代语言啊… 我说这句话, C++, JAVA, Objective-C, C#无一中枪, 不管是加入的早晚(其实都是较晚), 上述语言都已经有了使用匿名函数的办法. 对于Python, Ruby来说, 匿名函数就更不是什么新鲜事物了. 比较有意思的是, 作为静态语言的新事物, 上述语言都独立的发展了一套自己的Lambda语法, 而且各有特色, 并且最终的目的似乎都是让你搞不明白. C#的Lambda使用了 class MainClass { delegate int Actor (int leftParam, int rightParam); static int Call (Actor fun, int leftParam, int rightParam) { return fun(leftParam, rightParam); } public static void Main (string[] args) { Console.WriteLine ("{0}", Call((leftParam, rightParam) => { return leftParam * rightParam; }, 10, 10)); Console.WriteLine ("{0}", Call((leftParam, rightParam) => { return leftParam / rightParam; }, 10, 10)); Console.ReadKey(); } } 可以看到代码显著的简化, 当然, 其实这里的例子还是太过简单和生造了, 匿名函数最大的应用在于利用闭包特性来作为回调函数. 此时简化的往往不仅仅是一个函数, 甚至是一套完整的类. 而且, 在多个类似回调同时在一个类中使用的时候, 每个匿名函数各自独立, 不会出现需要在类的回调函数中用switch/if-else区分的丑陋代码. 这个回忆回忆以前你用过的任何GUI系统的Button回调, 大概就能理解. 而最佳的例子, 我也常常提起我很感叹的是, 在最近的Objective-C中加入的匿名函数(在Objective-C中被称为Block)对Objective-C接口的巨大影响, 几乎是整体性的对原有 异常传统的try, catch, finally模式. 面向对象部分主要有价值的特性都在这一部分. 我觉得最大的改进就在于C#和JAVA都没有使用C++(还有Objective-C)里面看似优美的接口与实现分离的策略. 很多时候我们都在说DRY(Don’t Repeat Yourself)是编程中排在第一的原则, 但是接口与实现分离, 即一个头文件用于声明, 一个实现文件用于实现的方式是彻头彻尾的Repeat. 这种方式就我了解是来自于C语言. C++和Objective-C都在一定程度上有向C语言兼容的负担, 于是都这样做了. 稍微追求点人性化的语言其实都不是类似C/C++和Objective-C那种方式. 这个序列可以从JAVA, C#一直写到lua, python, ruby, lisp. 类
构造函数初始化器支持类似C++初始化列表的构造函数初始化器. 语法也类似. namespace test { public class Point { public int x {get; set;} public int y {get; set;} } class MainClass { public static void Main (string[] args) { Point p = new Point { x = 1, y = 2 }; Console.WriteLine("Point: x={0}, y={1}", p.x, p.y); } } } 其中 访问器通过get, set关键字来定义属性的访问器, 并且通过忽略其中一个来实现只读和只写. 并且提供了一种自动生成属性的功能, 代码类似 可访问性传递原则可访问性只能越来越严格, 不能越来越放松. 比如 匿名类大家都知道匿名函数好用, 匿名类呢? 有了总比没有好吧. var p = new { x = 1, y = 2}; Console.WriteLine("Point: x={0}, y={1}", p.x, p.y); 注意, 我其实并没有定义一个Point类. 包含 扩展方法在Python, Ruby里面都能很方便的给已存在的类添加方法, 实现打猴子补丁的功能, 类也被称为开放类. 当时静态语言一般不行, C#通过扩展方法实现类似的功能, 只不过语法非常之不优美, 同为静态语言, 建议anders去学习一下Objective-C里面的类别(category), 这种使用在Objective-C中非常的普遍, 因为的确非常的方便. public static class ExtensionString { public static List<char> GetArray ( this string str) { List<char> result = new List<char>(); foreach (char c in str) { result.Add(c); } return result; } } class MainClass { public static void Main (string[] args) { string s = "abcdefg"; List<char> chars = s.GetArray (); foreach (char c in chars) { Console.Write(c); } } } 上例中, 我给标准的
不知道大家怎么看, 我是觉得有些不太直观和自然. 接口支持类似JAVA的接口. 关键字 public class MyClass : MyBase, IMyInterface1, IMyInterface2 { // class members } 也就是说, C#的继承体系基本上和JAVA一样, 只允许面向接口(规格)的多重继承, 不允许面向实现的多重继承. 这样好不好就见仁见智了(可参考多重继承不好的观点是错误的一文), 不过基本可以肯定的是, 的确要比C++不受限制的多重继承要难以滥用. 显示实现接口成员又一个新东西, 在实现类明确的制定一个函数是实现接口的某个函数式, 只能通过接口的多态性来调用该函数, 不允许使用类本身的对象来调用. 这相当于强制接口调用. 可删除对象虽然C#有垃圾回收机制, 但是大家都知道, 自动的垃圾回收机制使得程序员对内存及资源的掌握变弱了, C#使用可删除对象来解决这个问题, 提供了一个IDisposable接口, 限定必须实现 using System; namespace test { class MainClass { class NeedDispose : IDisposable { public NeedDispose() { Console.WriteLine("Constructing"); } public void Dispose () { Console.WriteLine("I'm Disposed"); } } public static void Main (string[] args) { NeedDispose dispose = new NeedDispose(); dispose.Dispose(); } } } 到这儿不算完, 都要手动调用的话, 那还不如C++那样出了对象存活范围就自动析构的对象, 于是多了个using的用法, 基本实现了C++使用对象的方式对资源的管理. // 语法一: using (NeedDispose dispose = new NeedDispose()) { } // 语法二: NeedDispose dispose = new NeedDispose(); using (dispose) { } 不过有点比较奇怪的是, 两种语法形式上有区别, 但是本质上竟然一样, 语法一的定义在出了using的scope以后竟然还有效, 也就是说, 上面那两种语句同时在一个scope中出现时, 会出现dispose的重定义冲突, 这个设计很奇怪. 结构
泛型C#在2.0后才加入了泛型, 作为一门新语言, 不知道这是为啥. 这也是为啥前面提到不知道从哪冒出来的封箱拆箱问题的原因? 约束类型这个类似C++中的曾经想要(但是没有)加入C++ 11的特性concept的更通用版本. 相当于给泛型类型一个约束限定, 只允许符合约束条件的模版类型. 这个特性的加入也体现了委员会和公司决定的语言之间的区别. 委员会保守, 公司激进, 而个人? 随意! 参考Python3… interface IMyInterface { void DoSomeThing(); } class MyClass : IMyInterface { public void DoSomeThing() { } } class MyGenericClass<T> where T : IMyInterface { } class MainClass { public static void Main (string[] args) { // MyGenericClass<int> x = new MyGenericClass<int>(); // compile error MyGenericClass<IMyInterface> y = new MyGenericClass<IMyInterface>(); MyGenericClass<MyClass> z = new MyGenericClass<MyClass>(); } } 语法上见上面的代码, 其中被注释掉的那一行会出现编译错误, 因为模版类型T被约束了. 只有 可空变量即可以等于null的变. 用类似 事件C#的事件本质上就是一种Gof的observer设计模式, 虽然语法上没有用subscribe这些传统概念. 并且因为C#委托的存在, 事件写起来还算是比较方便. 独创的用操作符 最后结论是, 假如不知道C#的那些高级特性, 那么把C#当作C++来用几乎没有任何问题, 而那些高级特性, 完全可以逐步的尝试. 而且JAVA和C#等语言的的确是进步了, 进步的方式就是把C++好的习惯用法, 编程规范和最佳实践, 直接变成语言特性(除了那所谓的完全面向对象). 一些改动虽然看起来很小, 甚至是语法糖, 但是的确是强制的(或者语言层面鼓励)写更好的代码. |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论