1.Java里的AClass.class得到的Class<T>类的对象对应C#的typeof(AClass)得到的Type类型的对象;(但是C#如果要反射创建对象是用Type对象的Assembly对象)
2.之前一直纠结于c#的委托类型变量是小驼峰,如Func1 func = Console.WriteLine;然后func("uu");的调用是不一致的,但是现在可以这样子func.Invoke("uu");这样就一致了(注意如果是void它是会返回null的)
而且经过反编译func("uu");和func.Invoke("uu");是一模一样的,因此不必担心用func.Invoke(..)是否会更耗资源(而且如果Func1是void那么var foo = func.Invoke(..)IDE会报错,这样就进一步防止了var foo 造成的变量浪费,注意Invoke和BeginInvoke一样是编译器自动加的是找不到声明的,理解它的作用即可,有点像java的Lombok);
而且后来也想了下大驼峰也有它的好处,就是不会和关键字冲突,比如关键字internal,现在假设有个方法最符合的名字就是internalC#里可以是Internal(...)不会冲突,否则得写成类似internal0(..)之类加点额外的东西也是挺丑陋的,变量或参数有额外的东西倒还好;
3.c#里void也是有类似java的void.class的,是typeof(void),C#用反射GetMethod("test")时也可以通过ReturnType判断其是否是等于typeof(void)
4.C#Delegate和delegate的区别就像是Java的Enum类和enum关键字的区别,所有enum关键字创建的枚举类都是继承Enum类,这点delegate也是一样的;
注意C#里Delegate和delegate是不一样的概念,但是String和string则可以认为是完全一样的(C++里的别名一样或C语音里的宏);
它们的主要区别是delegate关键字是用来声明类型,而string关键字是用来声明变量/引用;
5.Java的a instanceof A(or Base)对应C#的是a is A;(is也很好理解,比如我是人,我是一个对象,类型是人)
6.typeof(Delegate).IsAssignableFrom(typeof(Func1))在Java的反射里也是有相关的Api的;
7.Java里AClass.class是synchronized static method()锁的对象,AClass这个名字和C#一样是没有意义的,然后C#里比较对应AClass.class的是typeof(AClass)或aClass.GetType();
8.C#的Type比Java的Class要高明,因为Class实际上是Class<T>,然后比如Class<AClass> clazz = AClass.class;但是注意Class变量按理说本来就是用来代表一个类的元数据,那就不应该还有什么泛型的说法;所以C#的Type没有泛型是合理的;
java的Class<String> clazz = String.class;给人的感觉就是我clazz本来就是要获取一个类型的元素据,通过这个元素据能知道它对应的类型是什么,但是定义这个元数据的时候却还要先声明它装的是什么的元数据,有种多此一举的感觉;
9.Java里要转类型并且调用某方法必须得((MClass) obj).func();,而C#可以(obj as MClass).func();写法上更人性化;
10.C#的Console.WriteLine(..)方法可以以null值为参数(内部会进行判断参数如果是null则直接Console.WriteLine();),但是要写成Console.WriteLine((null as string));这样的形式,否则由于null对象可以对应String和char[]和其他很多类型会导致编译器编译的时候不知道该选择哪个Api进行调用;(所以重载在编译后其实二进制文件里该调用是会明确指定是调用的哪个方法);
11.C#比Java多个nameof,比如方法里有参数叫void test(string foo),那么在方法里用nameof(foo)或返回一个"foo",这个转换是在编译时就确定好了(就类似宏一样),不过它的用处一般是用于返回类的所有字段名,那么就可以用nameof防止后面重构导致字段名更改了;
12.C#里类的字段、属性、方法(无论是实例还是静态)、Event、构造方法、都可以称为成员,反射里对应MemberInfo,其他的所有的如FieldInfo和PropertyInfo和MethodInfo和EventInfo和ConstructorInfo都是继承这个父类;
13.Java里有受检异常(必须要求调用者try-catch或throws),C#里是没有的(C#里都是运行时异常);这里做个分析:
Java里的受检异常主要包括1.文件没有找到,2.字符集没有找到这些其实是完全可以规避后一定不会出现的异常(系统bug这种不考虑),对于这种类型的受检异常是完全不需要的;
但是Java里socket.getOutputStream().write(..)是可能抛出IOException这个受检异常,这个个人倒是觉得是应该显式要求调用write的人必须进行try-catch,因为这里的不可控的源头是自己不能消灭的,
像NullPointerException都是可以自己判断对方给的参数是否是null来规避,但是自己和对方连接,对方死机了或者怎么样导致write异常这个是完全不可控的,所以个人感觉这种还是需要受检异常的;
不过C#没有也行,毕竟这种完全不可控的场景非常少见,而且对于write的底层应该也是类似Socket在写流的时候判断自身连接状态,如果已经断开了写流自然抛异常,当成运行时的IllegalStateException来处理其实也行;
仔细想了下确实还是该有CE的,比如Mapper数据库操作,还是比较常出现因为配置的字段和数据库里不匹配所以导致异常,这个异常其实是应该声明为受检异常(又感觉不应该是受检异常,因为它虽然容易出现【程序猿不够细心】但是确实是可以
在开发阶段就避免,如果一个异常明明完全避免了却还要try-catch起来也是一个很坑爹的问题),所以想了下只有上面的写流时的异常应该为受检异常,因为完全不可控,但是不可控的异常其实理解为运行时异常是合理的,而且这样的完全不可控的异常毕竟很少,随着经验的提升是可以知道什么时候该try-catch的;
还有就是FileNotFoundException是受检异常,但是如果自己确认文件已经在了按理说其实就可以完全不用try-catch的(系统出问题不管这种跟死机了是无能为力的问题),但是还可能出现别人不小心删了,或者其他程序删了;所以这个异常是受检异常确实有它的道理,
不过感觉还是不能因为这种极其低的事件(正确处理还可以变为不可能发生的事件)而非得try-catch;这里最大的问题是网络交互,这种出错的概率比较大,只有这种才该成为受检异常;
【对于FileNotFondException自己理解为是可以完全避免的是因为创建File是通过构造方法创建,如果是Files.getFile(path),那么我从来没用过的话是可以有两种理解的,1是抛异常,2是返回null,而自己之所以认可了抛异常是因为经验,而对于
以写模式打开文件是可能失败的,比如被别的进程占用了这个是很多人都考虑不完善的而且也确实应该抛出受检异常IOException的子类,而且这个还没有办法先通过判断一下这个文件是否占用,然后再以写模式打开文件,这里涉及并发问题,因此对于这种确实不可控的
异常,确实应该是要提供CE的,所以C#这方面确实不够完善,不过受检异常也是会导致外层需要try-catch的异常多达几十种,这是因为中间服务代码的写者偷懒没有去手动try-catch自己那层就该处理的异常,不过这个对写代码的人的水平要求很高,很多人都不知道哪些异常应该自己这层处理掉而不要继续往上throws,所以干脆都全部往上抛,或全部catch为Exception统一处理,所以从现实的角度里,CE很多时候确实是摆设】
具体看这两篇文章:https://news.cnblogs.com/n/570148/
https://www.cnblogs.com/ioriwellings/p/6905863.html
14.Java里包名是小写且一般没有复数,如me.silentdoer.util而工具类则是Utils结尾,而C# “包名”是大写开头,且可以有和没有复数,而工具类是Util结尾;
这里个人认为是C#的要比Java的风格更好,比如一个包名如果是多个词组成,java就得写成abcUtil,这看起来就像是方法名一样很不美观;而C# 的NumberUtil很直观的理解为数字类的工具;
而NumberUtils给人的感觉是里面还有很多工具的意思;
15.C#里实现接口和重载抽象方法是不一样的,实现接口直接写public void test(){..}就行了,而Java里是要@Override;(这里C#将不同的场景分的很细);
这一块C#比java要灵活,首先是接口的方法实现不需要加任何的关键字和注解,默认就是必须实现;如果不实现则必须给方法加上abstract关键字,同时对于的类也是加abstract关键字(完整类也可以定义为抽象类),这点和java基本一致;
C#这一块分为这几个概念:接口方法(子类可直接实现不需要关键字)、抽象方法abstract(接口方法在子类没有实现则需要用abstract转换为抽象方法)、虚方法、new方法;
1.接口方法:之所以实现时不需要加override,是因为它是一个接口的方法,接口本身并没有实例成员,它和类是不一样的,因此不需要override;
2.抽象方法,它存在于类里,且有抽象方法的类必须声明为抽象类,且必须没有函数体;
抽象方法其实应该理解为没有内容的具体方法;接口方法只是方法名称的一个声明,所以抽象方法实现需要override,接口方法不需要;(或者这么理解需要override的其一定是继承而非实现)
3.虚方法,表示这个有函数体的方法还可以被子类进行重写;
4.new方法,它的出现其实就是为了弥补java里所有的和父类同签名的方法,以父类作为变量new子类对象,调用的永远是子类的方法的缺陷,如果一个方法的对象虽然是子类,但是它的变量是父类,那么调用时如果子类的该方法没有写new关键字那么会调用的是父类的方法实现;
16.在java里可以用try-finally来处理一些需要释放非托管资源的操作,如关闭文件(释放文件句柄),在C#里如果try块直接导致程序挂了那么finally是无效的(即是在Main方法里用try-finally无效,它已经是最上层的函数了),其他地方是可以的,如果要Main里也有效则用try-catch{...;throw;}也行,然后就是通过using来自动调用Dispose()方法也是一种办法(不过Dispose()在Main方法里也无效,因为它本质上是被编译器转换为了try-finally);
或者老老实实的try-catch(此时要不要finally都无所谓了)
17.Java的函数式接口对应着C#的委托,这里注意委托是可以写在namespace下而不一定是类里的,这里个人倒是觉得Java的比较好理解;不过可扩展性则是C#完胜,比如对于一个Max的委托/函数式接口:
现在需要有个能获取两个对象中最大的那个的委托/函数式接口:
C#是:
public delegate T Max<T>(T t1, T t2);
Java是:
@FunctionalInterface
public interface Max {
<T> T max(T t1, T t2);
} 如果只是这样当然都一样,而且Java由于语言特性跨度较小,特性之间更具有连续性所以更容易理解;
但是现在需要有个获取三个对象中最大的那个的委托/函数式接口
这个时候C#很容易就可以做到:public delegate T Max<T>(T t1, T t2, T t3);
而java则不得不定义一个叫public interface MaxForThree {...}这样的函数式接口(放在不同的包这更不合理); 如果后面还要有4个元素的,5个元素的,java要写死,而且名字得想半天,所以C#的委托还是很不错的;
不过C#的委托的调用方式使得它的命名有一定的不一致,如 Max max = SomeFunc;
max(a, b);这就不合方法是大驼峰规范了,不过好在还能写成max.Invoke(a, b);这样就不会不规范了;
还有一点,由于C#委托是用来承载方法的,所以它默认就是“静态的”,不能有static关键字也不能主动new,而且委托变量也不能用于lock【记忆中是这样】; 还有一点C#的委托其实都是多播委托MulticastDelegate,即一个委托变量可以承载多个方法(多个观察者),执行的是否却只能串行执行,因此多播委托在并发编程上用的不多还需要自己去写观察者模式然后产生事件时并行执行各个观察者;
18.新版本的C#编译器将不允许通过类对象来调用类的静态方法,这样可以很容易的看出哪个方法是实例方法,哪个是静态的;Java这方面还没有支持;(但是编译到dll后两者应该是一样的,类似委托变量m("aa");和m.Invoke("aa");都编译成extenr_xxx("aa"),只是IDE会做一层检查)
19.C#也有Intern方法,不过它是静态方法,而Java是实例方法;所以C#里如果某个字符串是通过如Split之类的方法生成的,那最好对其执行一个var split0 = string.Intern(str.Split(",")[0]);否则有可能在switch或HashMap上(对应C#的类,具体是啥忘了)会有问题;
20.Java的ThreadPoolExecutor其实不对应C#的ThreadPool而是对应TaskScheduler会比较好理解(TaskScheduler有ThreadPoolTaskScheduler和SynchronizationContextTaskScheduler前者是.net默认的)
21.C#对string类型做了比较符的优化(其实个人觉得不优化也不错),就是==符号对于string而言是Equals,而Java则是比较引用,如果C#里要比较两个字符串的引用值要用Object.ReferenceEquals(str1, str2);
22.C#的BigInteger对应Java BigInteger,但是java的BigDecimal对应C#的decimal;
23.C#和Java的switch对String类型的判断都是先判断引用,如果引用一样则匹配成功,如果引用不一样则进一步用Equals方法比较两个字符串的内容是否一样,内容一样则也是匹配成功;
24.C#和java一样,Map里对key的重复判定是引用和Equals两个一起判断的,只要引用或者内容一样就认为key是一样的,这里要注意,C#里不要再用Hashtable,这个是1.0里的老类,没有泛型;用System.Collections.Generic里面的集合类,Dictionary<T1, T2>是基于数组设计的(但是模拟了链表);
25.C#泛型和Java泛型,一个是真的,一个是假的(当初Java偷懒,导致现在已经不可能再弄真泛型了),具体可以看泛型擦除相关知识,其实java其他的不好都能容忍,就这个真心不能忍;
26.C#可空类型Nullable<AClass> m;对应Java的Optional<AClass> u;不过C#进行了一定的优化可以AClass?等价于Nullable<AClass>,而且Nullable<AClass> m = new AClass();可以直接这样赋值,其泛型还能是结构,这样int? b;b.HasValue可以预判断,如果有value则
可以直接int k = b;(C#某类型的可空类型和“不可空”类型变量直接的赋值其实就类似自动 装箱和拆箱)
27.C#里的value虽然是关键字,但是它要看在哪里及怎么用,比如在set {xx = value}那么这个value是关键字,如果是set {xx = this.value}那么这个value则不是关键字,即value是一个只在特定地方且特定的表述方式的“关键字”,它是可以作为成员名存在的:
public class Test {
private string key; private string value; public string Mm {
set {
// 这个value是指外部的赋值,而如果换成this.value则表示Test实例的value字段
Mm = value;
}
}
}
不过C#的属性的概念其实不太好懂,主要就是它存在不同情况需要不同理解,不像Java里就用setter和getter方法来做,但是Java的这种做法让get/set/is开头的方法称为了特殊方法,只能用于特殊的领域(很多框架对字段的反射都是通过判断方法名是不是以这些开头来做的,就有点感觉是方法中的特殊方法一样),而C#的属性如果只是从字面上来理解十分费劲,但是如果用反射一看其实就明白了:
class Program
{ public string Name
{
get;
set;
} public int Height
{
get;
} private decimal price; public decimal Price
{
get
{
return this.price + 1;
}
set
{
this.price = value / 2;
}
} // 报错,至少需要一个get或set,因为熟悉的特点就是为了提供数据的访问;
/*public object ObjUm
{ }*/ private double foo; private void test()
{
Console.WriteLine("MMMK");
} static void Main(string[] args)
{
// 注意这个方法是获取不到private成员的
var mse = typeof(Program).GetMembers();
foreach (var m in mse)
{
Console.WriteLine(m.Name);
if (m.Name == "Name")
{
// 输出Property
Console.WriteLine("##:" + m.MemberType);
}
if (m.Name == "Price")
{
Console.WriteLine("$$:" + m.MemberType);
}
}
Console.ReadKey();
}
}
上面代码的输出是:
所以其实C#里的Xx{get;}会生成一个get_Xx,而且它比Java的setXx更具有标识意义,因为C#里的标准方法定义里不应该有下划线,所以一些框架要反射的时候就可以根据get_和set_开头来操作属性值;
注意C#里不存在Java的boolean会是以is开头的情况,都是get_开头,而且可以这么写:
public bool IsSuccess
{
set
{
// 可以这么写,因为属性其实应该理解为类似字段一样的东西,然后编译器会为每个属性的get生成一个如get_Name(string name)方法来操作属性;
this.IsSuccess = value;
}
}
但是这里有个问题就是对于price字段和Price似乎都生成了一个存储空间??反正自己反射所有成员时得到了get_Price、set_Price和price和Price,这一块有点坑爹;
这句话经过测试是错误的:所以为了规避这种坑爹,最好get和set方法操作的成员就是属性,而不要自己再自定义一个字段来操作,public int Name { get {return this.Name + "uu"} set {this.Name = "ku" + value}}这种写法才是正确的,它不会导致死递归问题(set),get、set是方法的便捷写法;
那么C#里属性其实是一个坑爹的设定,属性它不是存储空间,它只是get_Name和set_Name两个方法的一个链接,调用foo.Name = ..;时链接到set_Name方法里;
还是得用一个unreachable string $$name bind Name的概念来描述为Name生成存储空间,$$name是不可被显示访问,也不可以被反射获取(有点像匿名隐形成员,只有通过属性才能连接到它,而且属性的连接[get,set]如果显式指定没有连接到这块匿名空间,那么编译器将不会生成这块属于类对象的匿名空间/匿名成员);
好吧,自动属性{get; set;}是4.5出来的,传统写法是:
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
所以应该从这个迁移过程来理解;
所以自动属性其实就是生成了一个类的特殊字段,他就是_age,不过这个_age的访问修饰符不再是private而是unreachable(我个人定义的。。),只有属性的get set才能访问它,反射也获取不到,这也是为什么反射出来的方法是get_Age,就是因为_age的命名方式;
所以C#里的下划线开头的标准其实只针对于用于属性的字段,其他不需要和属性关联的字段是可以不用_开头的;
28.由于C#是真泛型,所以是不允许typeof(Dictionary)这样的写法的,因为Dictionary不是一个可用类,typeof(Dictionary<int, string>)则可以(类型可以自定义),而Java是伪泛型所以HashMap.class是可以的,反而HashMap<Integer, String>.class是错误的;
29.C#里是名称空间的概念,而Java里是包的概念,其实从易用性和热部署等角度看Java的包概念比C#的名称空间好,而且C#里using只到包级别,而Java import到类级别,那么我看到程序里写了某个类我在文件头一瞄就知道它属于哪个包,C#则必须依赖IDE工具或者看这个类的源码才能知道,这在自己查资料时是很不方便的;然后就是热部署,Java的热部署可以精确到单个.class文件,比如新增了N个类只需添加这些类的.class即可,而C#则是必须更新整个.dll文件【所以C#对架构要求比较高,必须设计的时候就设计好哪些是底层库不经常改动然后合成一个.dll,而且要求主程序必须尽量精简,对于那些活动库则要分不同project开发】,当然.net的这种.dll(Assembly)有它的好处,就是它提供了更加灵活底层优化的可能,Java的优化只能到达单个类,而C#的可以将多个类进行整合优化(所以release编译会比较慢),具体可以看:https://www.zhihu.com/question/27997478/answer/38978762;当然根据自己现在的Java经验,其实Java线上也不会做热部署,都是在人少的时候通过Nginx将集群里的某台机子停掉,然后换上新的应用,然后启动后Nginx上给他配权重,然后再停其他的;这里要求服务的迭代必须是能对上个版本进行兼容,最简单的例子,上个版本a接口里要求参数可以为null,如果你新版本里这个参数要求必须不为null,那就和老版本不兼容了(还有登录态之类的);
30.Java里有内部类和静态内部类之分,内部类它要求先创建外围的对象,然后通过外围的对象来创建,类似:Inner test = new Outer().new Inner();不过这种的应用场景极其少见,而且它也完全可以用Java的静态内部类来代替【目前我看到的就一种方法描述里的参数描述可以用这种写法,因为必须先存在方法描述才能有其字段描述,不过字段描述类用静态内部类也是一样可以的】,而C#里没有Java里的内部类的概念,C# 里的内部类其实就是Java的静态内部类,而c#里的静态类则是指这个类不能被new(Java里是通过手动写private的构造方法实现,但是这个类仍然是实体类只是构造方法不能被外部调用而已,而C# 的静态类里面所有的成员都必须是静态的,同时C#也支持private的构造方法)
31.C#的类支持析构方法,写法就是和C++的一致,它有个好处就是可以一定程度上避免程序猿忘记释放非托管资源【Close、Dispose之类的方法来释放】那么想过的对象消失时能够通过析构方法进行释放非托管资源;而Java貌似没有,不过有个finalize方法不知道是不是可以用(好像是可以充当析构函数,不过注意这个函数的执行时间用户无法去控制,比如foo = null它不会令原先foo引用的对象的finalize()方法立刻执行);C#的析构函数和Java的finalize()方法都不是说对象不可再引用就是对象生命周期消失,而是还要看GC是否回收,
因为程序猿不能引用某个对象,但实际上这个对象仍然在GC里有注册,GC在回收时会调用要回收对象的析构函数或finalize函数;C++里的析构函数由于是手动delete某个对象所以是立刻释放的;
32.Java里的super对应C#里的base,并且Java里this(..)和super(..)不能同时出现,且单独出现时必须在构造方法的首行的规定在C#里的写法是AClass : this(..) {..}或AClass : base() {..},个人 认为C#的写法要更好,毕竟这样一看就能明白this和base不能同时出现;
33.对于静态方法的只有返回值是泛型的写法,C#是
public static Test<T>(int arg) {
if (arg > 0) {
return (T) new AClass();
} else {
return (T) new BClass();
}
}
调用是Program.Test<AClass>(3);这种写法; 而Java则是:
public static <T> T test(int arg) {
if (arg > 0) {
return (T) new AClass();
} else {
return (T) new BClass();
}
}
然后调用可以是Program.<AClass>test(3);这种写法,它的<AClass>是在方法名前面;
而且由于java是伪泛型,所以其实可以直接(AClass) Program.test(3); 对于throw也是一样,就是里面每个分支都没有return 都是throw