TA的每日心情 | 慵懒 2021-11-26 16:44 |
---|
签到天数: 488 天 连续签到: 1 天 [LV.9]妙领天机
如雷贯耳
- 积分
- 4611
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
作为一名集.NET、JavaEE、web前端于一身的全栈开发者,我自认为我对C#、java、javascript的认知都不算很浅的了,如果我们可以同时拥有 C# 和 Java 世界的最好特性,那会是什么样呢?
完美的编程语言并不存在,我希望我们可以在这一点上达成一致。开发新语言往往是为了克服另一种语言的弊端,又不可避免的在某些方面上健壮一些,却在另一些方面上存在不足。
C# 与 Java 都起源于 C/C++ 语言,他们在面向对象方面有许多相似之处。除了 Java JVM 和 C# .NET CLR 有许多相同结构上的相似性之外,他们各自的开发团队都有各自的发展方向,他们关注的是各自的语言应该成为什么样子。
我们并不想纠结于某一个语言比另一个语言好,我们只想罗列出 C# 开发者能用到而 Java 中没有的那些特性而已。
下面我们开始吧。
1. LINQ
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) && (numbers
< 100))
{
tens.Add(numbers);
}
}
return tens;
}
如果使用 LINQ 查询语法形式:
List<int> FilterTwoDigitNumbersWithLinq(List<int> numbers)=>(from a in numbers where (a > 9 && a < 100) select a).ToList();
或者是方法语法形式:
List<int> FilterNonTwoDigitNumbersWithLinq2(List<int> numbers)=> numbers.Where(a => a > 9 && a < 100).ToList();
这里两种语法都是正确的,唯一的区别就是查询语法看起来更像是 SQL 语句而方法语法使用 lambda 表达式(当然,看起来很像我们在 Java 里写的某些代码)
综述:LINQ 所依赖的许多特性,如 lambda 表达式(就 LINQ 来说非常有用),已经在 Java 中有了等效的实现,尽管我们可以使用流和 lambda 来查询数据,但 LINQ 简化了整个过程并且移除了很多在 Java 中存在的冗余代码。
2. Struct
C# 中的结构体类似于类。实际上,一个 struct 甚至可以被认为是一个“轻量级类”,因为它可以包含构造函数、常量、方法等等。一个结构体和一个类之间最大的区别在于结构是值类型,而类是引用类型。
相比于创建类,编写结构体最重要的好处是在构造一个值类型时比在构造引用类型时更容易确保值语义。如 Microsoft 的文档所述,“struct 类型的变量直接包含结构体的数据,而类类型的变量包含对数据的引用。”因此,对比使用类时,使用结构体的好处之一是,从代码的其他部分更改其值的唯一方法是将其作为参考进行显式传递。
微软的开发人员建议对于那些小于 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("两个点相加的结果是: {0}", (point1 + point2));
Console.ReadKey();
}
}
小结:很多情况下使用结构体可以节省内存分配和释放的时间,这确实很有吸引力。然而事实是值类型拥有自己的存储空间。无论结构体拥有如何明显的优点和缺点,这在 Java 中都不需要操心。
3. async/await
在一段代码中调用 async,或者更明确地调用方法,这个方法都会在另一个线程上执行,不会阻塞当前线程。当代码运行到 await 命令的时候,它会继续运行(await 的语句)。如果这时 async 代码还没有完成,那么执行中的程序会返回到调用点。
这有助于提高应用程序总体的响应速度,以及减少性能瓶颈。在应用程序访问 Web 和进行所有 UI 相关的活动时,使用异步程序非常重要。相对于以前的异步编程实现,使用 async/await 可以保留你代码的逻辑结构,而编译器则会担负起以前由开发者担负的重担。
示例:
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 关键字进行的实现相提并论。
4. Lazy<T> 类
无论使用 C# 还是 Java,很多人都已经实现了延迟初始化 (或实例化),因此对象要在第一次使用的时候才会被创建。有一种常见的例子是将延迟初始化用于应用程序启动的时候加载大量对象,但实际需要初始化的对象可能只有少数几个。这种情况下,我们希望辨别哪些是不需要在这里初始化的。只初始化那些确实需要初始化的对象可以提升应用程序的性能。
小结:最近,Lambda 表达式引入到 Java 8 之后,在 Java 中实现延迟加载(还有不少其它事情)变得更容易了。不过,在 C# 中我们可以使用语义化的 Lazy<T> 封装类来延迟初始化任何类库或用户指定的类型。
5. 一些等价的关键词
语言中的有用功能不一定像在 C# 中的 LINQ 或 Java 中的模块一样大。这里有一些可以帮助 C# 开发人员的关键字,它们在 Java 中并没有:
a. as
C# 中的 as 关键字会尝试安全地将对象转换为某个类型,如果不能转换的话,就返回 null。与 Java 的instanceof 几乎等同,但它是一个布尔值,如果类型匹配则返回 true,否则返回 false。
b. yield
在 C# 中使用 Yield 和 return yield 来进行自定义且状态化的迭代,不需要显式创建额外的类,也不需要创建临时集合。在 Java 中我们实现迭代最好的选择是使用外部库或使用 Java 8 引入的 Lambda 表达式。
c. var
var 是一种自动推断类型,也可以称之为万能接口,其实际类型由编译器决定,其功能相当于写一个显式类型 (比如 int, string 等)。它除了可以减少一些按键之外,var 还允许用于匿名类型,而匿名类型在 LINQ 中很常用。我们期待看到“var”标识,备受瞩目的 Java SE 9 将实现“将类型推导扩展到定义并初始化局部变量时。”
d. checked
C# 中,我们使用 checked 关键字显式启用对整型表达式的溢出检查。如果表达式的运算结果超出目标类型的范围,我们可以使用 checked 强制要求运行时抛出 OverflowException。这十分有用,因为常量表达式会在编译期进行溢出检查,而非常量表达式不会。
工具生态系统
Java 和 C# 之间存在大量的不同之外,当然,其中一些源于 Java 和 .NET 框架的不同。这些不同之处也导致了一些工具在兼容性方面的差异,比如 OverOps 在生产监控和错误跟踪方面的差异。
OverOps 向开发者展示生产中每个错误整个调用栈的全部源代码和变量状态。目前在 .NET 框架上并没有与之相同的内容,不过在接下来的几个月内会有一些变化。想了解更多信息,请点击这里加入我们 .NET Beta 的等候名单,如果你是 Java 开发者可以去 www.overops.com 查看演示。
最后的思考
在快结束时候,我们这里提到的大部分功能都在代码长度和简洁程度方面对 C# 开发者有所帮助,这些代码不能在 Java 中编写。事实上这些特性也或多或少说明了 Java 语言冗长的问题,包括最近版本更新带来的 Lambda 表达式。诚然,很多这些存在于 C# 而不存在于Java 中的特性在常规使用中提供了比使用 Lambda 更简洁的语法。
再次说明,我们不想卷入没完没了的关于哪种言更好的争论,我们只是在这里指出两种语言之间的一些区别。我们是否遗漏了某些你希望 Java 拥有的特性?请在评论中告诉我们!
英文原文出处:http://blog.takipi.com/c-vs-java-5-irreplaceable-c-features-wed-kill-to-have-in-java/ |
|