加载中

If we could have the best of both worlds between C# and Java, what would that look like?

The perfect programming language doesn’t exist. I hope we can agree on that, if nothing else. New languages are often developed in response to the shortcomings of another, and each is inevitably stronger in some ways and weaker in others.

C# and Java both stemmed from C/C++ languages, and they have a lot in common beyond both being Object-oriented. In addition to some structural similarities between Java’s JVM and C#’s .NET CLR, each advanced on its own path with their respective development teams focused on different visions of what the language should be.

We don’t want to get lost in the argument of which language is better than the other, we just want to outline some of the features that developers in C# are using that we don’t have available to us in Java.

Let’s get started.

如果我们可以同时拥有 C# 和 Java 世界的最好特性,那会是什么样呢?

完美的编程语言并不存在,我希望我们可以在这一点上达成一致。开发新语言往往是为了克服另一种语言的弊端,又不可避免的在某些方面上健壮一些,却在另一些方面上存在不足。

C# 与 Java 都起源于 C/C++ 语言,他们在面向对象方面有许多相似之处。除了 Java JVM 和 C# .NET CLR 有许多相同结构上的相似性之外,他们各自的开发团队都有各自的发展方向,他们关注的是各自的语言应该成为什么样子。

我们并不想纠结于某一个语言比另一个语言好,我们只想罗列出 C# 开发者能用到而 Java 中没有的那些特性而已。

下面我们开始吧。

1. LINQ

LINQ (Language-Integrated Query) was introduced to C# in 2007 to help developers query data from various sources. With it, we can write queries without needing to take into consideration the appropriate syntax for the specific database being called on. A component of LINQ, the LINQ provider, converts the query to a format that’s readable by the underlying source. For example, if we need to query data from a SQL database, the LINQ to SQL provider will convert the LINQ query into T-SQL so that the database can understand it.

To perform a query operation in LINQ, first the database is obtained, then the query is created and finally it’s executed. In LINQ to Object queries, this can be as simple as a single line of code, rather than writing complicated iterations of nested for each loops.

For example, let’s look at this code for filtering 2-digit numbers from a list in C#.

1. LINQ

LINQ (Language-Integrated Query,语言集成查询) 于 2007 年引入到 C#,以帮助开发人员从各种数据源查询数据。使用它,我们可以在无需考虑正在调用的特定数据库的语法来编写查询语句。LINQ provider 所提供的一个组件将查询转换为下层数据源可读的格式。例如,如果我们需要从 SQL 数据库查询数据,LINQ to SQL provider 程序将把 LINQ 查询转换成 T-SQL,以便数据库可以理解它。

要在 LINQ 中执行查询操作,首先获取数据库,然后创建查询,最后执行查询。在 LINQ to Object 查询中,这可能仅像一样代码一样简单,而不是为每个循环编写嵌套的复杂迭代。

例如,我们来看看这个代码,用于在 C# 中从列表中过滤 2 位数。

First, without using 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;
}

And then using LINQ in query syntax:

List<int> FilterTwoDigitNumbersWithLinq(List<int> numbers)
{
    return (from a in numbers
            where (a > 9 && a < 100)
            select a).ToList();
}

And method syntax:

List<int> FilterNonTwoDigitNumbersWithLinq2(List<int> numbers)
{
    return numbers.Where(a => a > 9 && a < 100).ToList();
}

Both syntaxes here are correct, the only real difference is that the query syntax looks more like SQL and the method syntax uses lambda expressions (and thus, looks like something we might write in Java).

Bottom Line: Many of the features that LINQ relies on, such as lambdas, are useful in their own right and already have equivalents that were implemented in Java. So, while we can use streams and lambdas for querying data, LINQ streamlines the process and removes much of the verbosity that still exists in Java.

首先,在不使用 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 中存在的冗余代码。

2. Struct

Structs in C# are used similarly to classes. In fact, a struct can even be considered to be a “lightweight class” itself as it can contain constructors, constants, methods and more. The biggest difference between a struct and a class is that structs are value-types while classes are reference-types.

The most significant benefit of writing a struct over creating a class is that it is easier to ensure value semantics when constructing a value-type than it is when constructing a reference-type. As stated in Microsoft’s documentation, “a variable of a struct type directly contains the data of the struct, whereas a variable of a class type contains a reference to the data.” So, one of the benefits of using a struct over a class is that the only way to alter its value from other parts of the code is by explicitly passing it as a reference.

2. Struct

C# 中的结构体类似于类。实际上,一个 struct 甚至可以被认为是一个“轻量级类”,因为它可以包含构造函数、常量、方法等等。一个结构体和一个类之间最大的区别在于结构是值类型,而类是引用类型。

相比于创建类,编写结构体最重要的好处是在构造一个值类型时比在构造引用类型时更容易确保值语义。如 Microsoft 的文档所述,“struct 类型的变量直接包含结构体的数据,而类类型的变量包含对数据的引用。”因此,对比使用类时,使用结构体的好处之一是,从代码的其他部分更改其值的唯一方法是将其作为参考进行显式传递。

Developers at Microsoft recommend using a struct in place of a class only for types which are smaller than 16 bytes, are immutable, are short-lived and are not frequently boxed. Under these circumstances, using a struct may also be slightly more efficient than using a class because it will more likely be stored in the stack rather than in the heap.

Example:

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();
        }
}

Bottom Line: In many situations, writing a struct can appear to save time on memory allocation and deallocation and, thus, be more appealing. The truth is, though, that value-types are stored wherever they are owned. Regardless of the apparent benefits or drawbacks of using structs, we don’t have to worry about any of this when it comes to 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 中都不需要操心。

3. Async/Await

By calling async on a code part, or more specifically on a method, that method will be executed on a separate thread so as to not block the current thread. When the code reaches the await command, it will continue running. If at that point, the async code hasn’t finished, then the execution will return to its calling method.

This can help improve the overall responsiveness of your application and help to reduce performance bottlenecks. Using asynchronous programming is very important for applications when trying to access the web and for all UI-related activities. Compared to previous techniques of implementing asynchronous programming, the use of async/await preserves the logical structure of your code and the compiler does the heavy lifting that used to be required of the developer.

3. Async/Await

在一段代码中调用 async,或者更明确地调用方法,这个方法都会在另一个线程上执行,不会阻塞当前线程。当代码运行到 await 命令的时候,它会继续运行(await 的语句)。如果这时 async 代码还没有完成,那么执行中的程序会返回到调用点。

这有助于提高应用程序总体的响应速度,以及减少性能瓶颈。在应用程序访问 Web 和进行所有 UI 相关的活动时,使用异步程序非常重要。相对于以前的异步编程实现,使用 async/await 可以保留你代码的逻辑结构,而编译器则会担负起以前由开发者担负的重担。

Example:

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.");
        }
}

The output:

// 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

Bottom line: CompletableFutures undoubtedly brought us closer to having equivalent capabilities in asynchronous programming in C# and Java. Still, the complicated nature of using it makes it no match for the ease with which the async/await keywords can be implemented.

示例:

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> Class

Whether working in C# or in Java, many of us have implemented lazy initialization (or instantiation) so that an object is not created until the first time that it will be used. One of the more common instances that lazy initialization is used for is when an application loads many objects upon launching but only requires a few of them initially. In this case, we want to instruct unnecessary objects to initialize only when needed to improve the performance of our application.

Bottom Line: Implementing lazy initialization recently became much easier in Java (as did many other things) when lambda expressions were introduced in Java 8. Still, in C# we can use the Lazy<T> wrapper class which provides the semantics of lazy initialization for any class library or user-specified type.

4. Lazy<T> 类

无论使用 C# 还是 Java,很多人都已经实现了延迟初始化 (或实例化),因此对象要在第一次使用的时候才会被创建。有一种常见的例子是将延迟初始化用于应用程序启动的时候加载大量对象,但实际需要初始化的对象可能只有少数几个。这种情况下,我们希望辨别哪些是不需要在这里初始化的。只初始化那些确实需要初始化的对象可以提升应用程序的性能。

小结:最近,Lambda 表达式引入到 Java 8 之后,在 Java 中实现延迟加载(还有不少其它事情)变得更容易了。不过,在 C# 中我们可以使用语义化的 Lazy<T> 封装类来延迟初始化任何类库或用户指定的类型。

5. Some Keyword Equivalencies

Useful features in languages don’t have to be as big of an undertaking as implementing something like LINQ in C# or modules in Java. Here are some keywords that help C# developers that we don’t have in Java:

a. as

The as keyword in C# attempts to safe-cast an object to a type, and if it can’t it returns null. Java’s instanceof is almost comparable, but it is a boolean that returns true if the types match and false if they don’t.

5. 一些等价的关键词

语言中的有用功能不一定像在 C# 中的 LINQ 或 Java 中的模块一样大。这里有一些可以帮助 C# 开发人员的关键字,它们在 Java 中并没有:

a. as

C# 中的 as 关键字会尝试安全地将对象转换为某个类型,如果不能转换的话,就返回 null。与 Java 的instanceof 几乎等同,但它是一个布尔值,如果类型匹配则返回 true,否则返回 false。

b. Yield

Yield and return yield are used in C# to perform custom and stateful iterations without an explicit extra class and without the need to create a temporary collection. Our best options for implementing iterations in Java seem to be accessing an external library or using lambdas which were introduced in Java 8.

c. var

Var is an implicit type that is determined by the compiler and is functionally equivalent to writing an explicit type (i.e. int, string, etc.). Aside from saving a few extra keystrokes, var allows for anonymous types which are most typically used in LINQ queries. We’re expecting to see a “var” identifier implemented in the highly anticipated Java SE 9 which will “extend type inference to declarations of local variables with initializers.”

b. Yield

在 C# 中使用  Yield 和 return yield 来进行自定义且状态化的迭代,不需要显式创建额外的类,也不需要创建临时集合。在 Java 中我们实现迭代最好的选择是使用外部库或使用 Java 8 引入的 Lambda 表达式。

c. var

Var 是一种隐式类型,其实际类型由编译器决定,其功能相当于写一个显式类型 (比如 intstring 等)。它除了可以减少一些按键之外,var 还允许用于匿名类型,而匿名类型在 LINQ 中很常用。我们期待看到“var”标识,备受瞩目的 Java SE 9 将实现“将类型推导扩展到定义并初始化局部变量时。”

返回顶部
顶部