完美的编程语言并不存在,我希望我们可以在这一点上达成一致。开发新语言往往是为了克服另一种语言的弊端,又不可避免的在某些方面上健壮一些,却在另一些方面上存在不足。
C# 与 Java 都起源于 C/C++ 语言,他们在面向对象方面有许多相似之处。除了 Java JVM 和 C# .NET CLR 有许多相同结构上的相似性之外,他们各自的开发团队都有各自的发展方向,他们关注的是各自的语言应该成为什么样子。
我们并不想纠结于某一个语言比另一个语言好,我们只想罗列出 C# 开发者能用到而 Java 中没有的那些特性而已。
下面我们开始吧。
LINQ (Language-Integrated Query,语言集成查询) 于 2007 年引入到 C#,以帮助开发人员从各种数据源查询数据。使用它,我们可以在无需考虑正在调用的特定数据库的语法来编写查询语句。LINQ provider 所提供的一个组件将查询转换为下层数据源可读的格式。例如,如果我们需要从 SQL 数据库查询数据,LINQ to SQL provider 程序将把 LINQ 查询转换成 T-SQL,以便数据库可以理解它。
要在 LINQ 中执行查询操作,首先获取数据库,然后创建查询,最后执行查询。在 LINQ to Object 查询中,这可能仅像一样代码一样简单,而不是为每个循环编写嵌套的复杂迭代。
例如,我们来看看这个代码,用于在 C# 中从列表中过滤 2 位数。
首先,在不使用 LINQ 的情况下:
List<int> FilterTwoDigitNumbersWithoutLinq(List<int> numbers) { var tens = new List<int>(); for (var i=0; i < numbers.Count(); i++) { if ((9 < numbers[i]) && (numbers[i] < 100)) { tens.Add(numbers[i]); } } return tens; }
如果使用 LINQ 查询语法形式:
List<int> FilterTwoDigitNumbersWithLinq(List<int> numbers) { return (from a in numbers where (a > 9 && a < 100) select a).ToList(); }
或者是方法语法形式:
List<int> FilterNonTwoDigitNumbersWithLinq2(List<int> numbers) { return numbers.Where(a => a > 9 && a < 100).ToList(); }
这里两种语法都是正确的,唯一的区别就是查询语法看起来更像是 SQL 语句而方法语法使用 lambda 表达式(当然,看起来很像我们在 Java 里写的某些代码)
综述:LINQ 所依赖的许多特性,如 lambda 表达式(就 LINQ 来说非常有用),已经在 Java 中有了等效的实现,尽管我们可以使用流和 lambda 来查询数据,但 LINQ 简化了整个过程并且移除了很多在 Java 中存在的冗余代码。
微软的开发人员建议对于那些小于 16 字节、生命周期短、不改变的而且不常装箱的类型,使用结构体(struct)而不是类(class)。在这种情况下,使用结构体可能会比使用类更有效率,因为它会保存在栈而不是堆中。
比如:
public struct Point { public int X; public int Y; public Point(int X, int Y) { this.X = X; this.Y = Y; } public static Point operator +(Point p1, Point p2) { return new Point(p1.X + p2.X, p1.Y + p2.Y); } public override string ToString() { return ($"({X}, {Y})"); } } class Program { static void Main(string[] args) { Point point1 = new Point(1, 5); Point point2 = new Point(2, 3); Console.WriteLine("The addition of both points will result in: {0}", (point1 + point2)); Console.ReadKey(); } }
小结:很多情况下使用结构体可以节省内存分配和释放的时间,这确实很有吸引力。然而事实是值类型拥有自己的存储空间。无论结构体拥有如何明显的优点和缺点,这在 Java 中都不需要操心。
示例:
class Program { public static void Main() { Console.WriteLine("Hey David, How much is 98745 divided by 7?"); Task<int> david = ThinkAboutIt(); Console.WriteLine("While he thinks, lets chat about the weather for a bit."); Console.WriteLine("Do you think it's going to rain tomorrow?"); Console.WriteLine("No, I think it should be sunny."); david.Wait(); var davidsAnswer = david.Result; Console.WriteLine($"David: {davidsAnswer}"); Console.ReadKey(); } private static async Task<int> ThinkAboutIt() { await ReadTheManual(); Console.WriteLine("Think I got it."); return (98745 / 7); } private static async Task ReadTheManual() { string file = @"D:\HowToCalc.txt"; Console.WriteLine("Reading a manual."); using (StreamReader reader = new StreamReader(file)) { string text = await reader.ReadToEndAsync(); } Console.WriteLine("Done."); } }
输出:
// Possible Output: Hey David, How much is 98745 divided by 7? Reading a manual. While he thinks, lets chat about the weather for a bit. Do you think it's going to rain tomorrow? No, I think it should be sunny. Done. Think I got it. David: 14106
概要:CompletableFutures 无疑可以使我们更趋近于拥有等效于 C# 和 Java 所拥有的异步编程中的能力。尽管如此,使用它所带来的复杂性使其易用度不能与使用 async /await 关键字进行的实现相提并论。
无论使用 C# 还是 Java,很多人都已经实现了延迟初始化 (或实例化),因此对象要在第一次使用的时候才会被创建。有一种常见的例子是将延迟初始化用于应用程序启动的时候加载大量对象,但实际需要初始化的对象可能只有少数几个。这种情况下,我们希望辨别哪些是不需要在这里初始化的。只初始化那些确实需要初始化的对象可以提升应用程序的性能。
小结:最近,Lambda 表达式引入到 Java 8 之后,在 Java 中实现延迟加载(还有不少其它事情)变得更容易了。不过,在 C# 中我们可以使用语义化的 Lazy<T> 封装类来延迟初始化任何类库或用户指定的类型。
在 C# 中使用 Yield 和 return yield 来进行自定义且状态化的迭代,不需要显式创建额外的类,也不需要创建临时集合。在 Java 中我们实现迭代最好的选择是使用外部库或使用 Java 8 引入的 Lambda 表达式。
Var 是一种隐式类型,其实际类型由编译器决定,其功能相当于写一个显式类型 (比如 int, string 等)。它除了可以减少一些按键之外,var 还允许用于匿名类型,而匿名类型在 LINQ 中很常用。我们期待看到“var”标识,备受瞩目的 Java SE 9 将实现“将类型推导扩展到定义并初始化局部变量时。”
评论删除后,数据将无法恢复
评论(2)