Crouching Tiger, Hidden Dragon
Java vs. C # ... What could be better than an eternal argument? No, this article is not dedicated to the next benchmark, and it’s not even a holy war, it’s not even worth the question: “who is cooler”.
Each task has its own tool. It makes no sense to compare C # and Ruby, for example. their intended purpose is completely different, and nature as well. However, it is C # and Java that are the closest in their philosophy.
Very often, colleagues writing in Java are not even aware of the many (!!!) things that C # provides (or, conversely, does not).
If you are interested in looking at C # and Java without subjectivity , as well as finding out the internal structure of one or another feature, then go ahead.
The C # language appeared in 2001, and its development was started back in 1999. Then it was very similar to Java 1.4. However, modern C #, which we know, should begin to be considered with version 2.0 (which corresponds to the release time of Java 5).
There is an opinion that C # takes a lot from Java. However, I strongly disagree with this. In my opinion, C # is largely C “with objects”, or C ++ “with a human face”.
I hope that the article and the arguments in it will confirm this statement.
Retell Wikipedia I will not. Therefore, we will immediately move on and go through the key differences and advantages.
First, we will look at the capabilities of the JVM and Common Language Runtime (CLR) themselves, then we will look at the syntactic sugar C #.
Both .NET and Java use bytecode. Of course, besides the format itself, there is one very important difference - polymorphism.
CIL (Common Intermediate Language, aka MSIL, aka IL) is a bytecode with polymorphic (generalized) instructions.
So, if Java uses a separate instruction for each type of operation with different types (for example: fadd - addition of 2 float, iadd - addition of 2 integer), then in CIL for each type of operation there is only one instruction with polymorphic parameters ( for example, there is only one instruction - add , which adds both float and integer). The issue of deciding the generation of the corresponding x86 instructions rests with the JIT.
The number of instructions on both platforms is about the same. Comparing the list of Java and CIL bytecode commands , it can be seen that Java has 206 and CIL by 232, but we do not forget that in Java many commands simply repeat each other's functionality.
As you know, Java uses the type erasure mechanism, i.e. generics support is provided only by the compiler, but not by runtime, and after compilation information about the type itself will not be available.
For example, the code:
Print true .
In this case, instead of a generic type T , an instance of an object of type java.lang.Object is created .
Will be converted to:
Thus, all type safety policies are destroyed instantly.
.NET, by contrast, has full support for generics, both compile-time and run-time.
So what needs to be implemented for full support for generics in Java? Instead, we’ll better see what the .NET development team did:
Those. generics are available not only at compile time, but also at runtime without losing or changing type information. The need for boxing / unboxing also disappears.
Java is a fully OO language . One can argue with that. And here's why: primitive types (integer, float, etc.) are not inherited from java.lang.Object. Therefore, generics in Java do not support primitive types.
But in a truly OO language, everything is object .
This is not the only limitation. It is also impossible to create your own primitive types.
C # allows you to do this. The name of these structures is struct .
For instance:
Also, generics in C # allow you to use value types (int, float, etc.)
If you need to write in Java like this:
But you can’t do this:
C # allows the use of primitive types.
Now we come to the topic of type hierarchy in .NET.
There are 3 types of types in .NET: value, reference, and pointer types.
So, value type is an analogue of primitive type from Java. However, it inherits from System.Object , lives not on the heap, but on the stack (and now the caveat: the location of the value type depends on its life cycle, for example, boxing automatically takes place when participating in the closure).
Reference type - This is the same as reference types in Java.
Pointer type - is the most unusual property of .NET. The fact is that the CLR allows you to work with pointers directly!
For instance:
Very similar to C ++ code, right?
First, let's determine what C # can do:
Properties in C # represent syntactic sugar since during compilation, they turn into methods like GetXXX , SetXXX . However, information about the very concept of a property is stored in metadata, so from any other language that supports the property, we can access it only as object.PropertyX , and not object.GetPropertyX .
For instance:
Will be converted to:
Delegates are analogous to method pointers in C / C ++. However, they are type-safe. Their main purpose is callback functions, as well as working with events.
At the same time, delegates in .NET are full-fledged objects .
Since the advent of DLR (Dynamic Language Runtime - the foundation for dynamic languages in .NET), as well as dynamic in C # 4 - has been playing a key role in supporting dynamism. For a more detailed introduction, I advise you to read my article Diving into the depths of C # dynamic .
This approach is fundamentally different from the Da Vinci Java project, because in the latter they are trying to expand the VM itself.
Consider an example in C #:
And also in C:
So what do we see? If in C we can pass a pointer to a function with other types of parameters (say float arg1, float arg2), then in C # this is not possible. In C #, delegates pass not only signature and type checks at compile time, but also in runtime.
Events are necessary for the implementation of event-oriented programming. Of course, you can get by with EventDispatcher, or the Publisher / Subscriber pattern. However, native language support provides significant benefits. One of which is type safety .
For instance:
Anonymous methods greatly simplify life - the class structure remains clean, i.e. no need to create separate extra methods in the class itself.
Change the above example with binary operations using anonymous methods:
Isn't it shorter and cleaner?
Now consider lambda expressions .
An lambda expression is an anonymous function that contains expressions and operators, and can also be used to create delegates or an expression tree.
But what will the event example look like? Very simple:
Well, we have reduced the code even more and it already becomes like a functional style (!!!). Yes, C # is also a functional language, as functions are first class objects.
Lambda expressions, as well as expression trees, were created with LINQ (Language Integrated Query).
Still not familiar with LINQ? Want to see what the famous MapReduce will look like on LINQ?
Or use the SQL-like syntax:
In this example, we see both LINQ (GroupBy (). Select (). Where (), etc.) and anonymous classes -
Hmm ... what else is used here? The answer is simple - a powerful type inference system.
The main role here is played by the var keyword . C ++ 11 has a similar auto construct .
So without type inference, we would have to write like this:
[The compiler generated this method for us]
To describe all the other features of C #, its compiler settings, etc. more than one article needs to be devoted.
Meanwhile, C # 5 - which is already available and will be officially released soon - adds asynchronous programming , which also appeared in C ++ 11!
C # and Java are powerful languages as well as powerful platforms (.NET and Java). As I wrote at the beginning of the article, there is a tool for each task.
C # - is not a continuation or copying of Java. Even when it was developed at Microsoft, its code name was COOL (C-like Object Oriented Language). How many times has this article cited an analogy with C / C ++? A sufficient amount.
I hope that my article helped to solve (at least a little) the issue of the difference between languages and platforms.
Thanks for attention!
Each task has its own tool. It makes no sense to compare C # and Ruby, for example. their intended purpose is completely different, and nature as well. However, it is C # and Java that are the closest in their philosophy.
Very often, colleagues writing in Java are not even aware of the many (!!!) things that C # provides (or, conversely, does not).
If you are interested in looking at C # and Java without subjectivity , as well as finding out the internal structure of one or another feature, then go ahead.
▌ A bit of history
The C # language appeared in 2001, and its development was started back in 1999. Then it was very similar to Java 1.4. However, modern C #, which we know, should begin to be considered with version 2.0 (which corresponds to the release time of Java 5).
There is an opinion that C # takes a lot from Java. However, I strongly disagree with this. In my opinion, C # is largely C “with objects”, or C ++ “with a human face”.
I hope that the article and the arguments in it will confirm this statement.
Retell Wikipedia I will not. Therefore, we will immediately move on and go through the key differences and advantages.
First, we will look at the capabilities of the JVM and Common Language Runtime (CLR) themselves, then we will look at the syntactic sugar C #.
▌ Episode I: Bytecode
Both .NET and Java use bytecode. Of course, besides the format itself, there is one very important difference - polymorphism.
CIL (Common Intermediate Language, aka MSIL, aka IL) is a bytecode with polymorphic (generalized) instructions.
So, if Java uses a separate instruction for each type of operation with different types (for example: fadd - addition of 2 float, iadd - addition of 2 integer), then in CIL for each type of operation there is only one instruction with polymorphic parameters ( for example, there is only one instruction - add , which adds both float and integer). The issue of deciding the generation of the corresponding x86 instructions rests with the JIT.
The number of instructions on both platforms is about the same. Comparing the list of Java and CIL bytecode commands , it can be seen that Java has 206 and CIL by 232, but we do not forget that in Java many commands simply repeat each other's functionality.
▌ Episode III: Generics (parameterized types || parametric polymorphism)
As you know, Java uses the type erasure mechanism, i.e. generics support is provided only by the compiler, but not by runtime, and after compilation information about the type itself will not be available.
For example, the code:
List strList = new ArrayList();
List intList = new ArrayList();
bool areSame = strList.getClass() == intList.getClass();
System.out.println(areSame);
Print true .
In this case, instead of a generic type T , an instance of an object of type java.lang.Object is created .
List strList = new ArrayList();
strList.add("stringValue");
String str = strList.get(0);
Will be converted to:
List list = new ArrayList();
list.add("stringValue");
String x = (String) list.get(0);
Thus, all type safety policies are destroyed instantly.
.NET, by contrast, has full support for generics, both compile-time and run-time.
So what needs to be implemented for full support for generics in Java? Instead, we’ll better see what the .NET development team did:
- Common type system generics support for cross-language interaction
- Full support from the Reflection API
- Adding new classes / methods to the base classes (Base Class Library)
- Changes to the format and metadata tables themselves
- Changes in runtime memory allocation algorithm
- Adding New Data Structures
- Genics support by JIT (code-sharing)
- Changes in CIL format (new bytecode instructions)
- Support from the CIL Verifier
Those. generics are available not only at compile time, but also at runtime without losing or changing type information. The need for boxing / unboxing also disappears.
▌ Episode IV: Types
But in a truly OO language, everything is object .
This is not the only limitation. It is also impossible to create your own primitive types.
C # allows you to do this. The name of these structures is struct .
For instance:
public struct Quad
{
int X1;
int X2;
int Y1;
int Y2;
}
Also, generics in C # allow you to use value types (int, float, etc.)
If you need to write in Java like this:
List intList = new ArrayList();
But you can’t do this:
List intList = new ArrayList();
C # allows the use of primitive types.
Now we come to the topic of type hierarchy in .NET.
There are 3 types of types in .NET: value, reference, and pointer types.
So, value type is an analogue of primitive type from Java. However, it inherits from System.Object , lives not on the heap, but on the stack (and now the caveat: the location of the value type depends on its life cycle, for example, boxing automatically takes place when participating in the closure).
Reference type - This is the same as reference types in Java.
Pointer type - is the most unusual property of .NET. The fact is that the CLR allows you to work with pointers directly!
For instance:
struct Point
{
public int x;
public int y;
}
unsafe static void PointerMethod()
{
Point point;
Point* p = &point;
p->x = 100;
p->y = 200;
Point point2;
Point* p2 = &point2;
(*p2).x = 100;
(*p2).y = 200;
}
Very similar to C ++ code, right?
▌ Episode V: C # Features
First, let's determine what C # can do:
- Properties (including automatic)
- Delegates
- Events
- Anonymous methods
- Lambda expressions
- LINQ
- Expression trees
- Anonymous classes
- Powerful type inference
- Operator Overload
- Indexers
- ... much more
Properties in C # represent syntactic sugar since during compilation, they turn into methods like GetXXX , SetXXX . However, information about the very concept of a property is stored in metadata, so from any other language that supports the property, we can access it only as object.PropertyX , and not object.GetPropertyX .
For instance:
public class TestClass
{
public int TotalSum
{
get
{
return Count * Price;
}
}
//автоматическое свойство - компилятор сам сгенерирует поля
public int Count
{
get;
set;
}
public int Price
{
get
{
return 50;
}
}
}
Will be converted to:
public class TestClass
{
/*
*весь остальной код
*/
private int k__BackingField;
//автоматическое свойство - компилятор сам сгенерирует поля
public int Count
{
get { return k__BackingField; }
set { k__BackingField = value; }
}
}
Delegates are analogous to method pointers in C / C ++. However, they are type-safe. Their main purpose is callback functions, as well as working with events.
At the same time, delegates in .NET are full-fledged objects .
Since the advent of DLR (Dynamic Language Runtime - the foundation for dynamic languages in .NET), as well as dynamic in C # 4 - has been playing a key role in supporting dynamism. For a more detailed introduction, I advise you to read my article Diving into the depths of C # dynamic .
This approach is fundamentally different from the Da Vinci Java project, because in the latter they are trying to expand the VM itself.
Consider an example in C #:
public class TestClass
{
public delegate int BinaryOp(int arg1, int arg2);
public int Add(int a, int b)
{
return a + b;
}
public int Multiply(int first, int second)
{
return first * second;
}
public void TestDelegates()
{
BinaryOp op = new BinaryOp(Add);
int result = op(1, 2);
Console.WriteLine(result);
//выведет: 3
op = new BinaryOp(Multiply);
result = op(2, 5);
Console.WriteLine(result);
//выведет: 10
}
}
And also in C:
int Add(int arg1, int arg2)
{
return arg1 + arg2;
}
void TestFP()
{
int (*fpAdd)(int, int);
fpAdd = &Add; //указатель на функцию
int three = fpAdd(1, 2); // вызываем функцию через указатель
}
So what do we see? If in C we can pass a pointer to a function with other types of parameters (say float arg1, float arg2), then in C # this is not possible. In C #, delegates pass not only signature and type checks at compile time, but also in runtime.
Events are necessary for the implementation of event-oriented programming. Of course, you can get by with EventDispatcher, or the Publisher / Subscriber pattern. However, native language support provides significant benefits. One of which is type safety .
For instance:
public class MyClass
{
private string _value;
public delegate void ChangingEventhandler(string oldValue);
public event ChangingEventhandler Changing;
public void OnChanging(string oldvalue)
{
ChangingEventhandler handler = Changing;
if (handler != null)
handler(oldvalue);
}
public string Value
{
get
{
return _value;
}
set
{
OnChanging(_value);
_value = value;
}
}
public void TestEvent()
{
MyClass instance = new MyClass();
instance.Changing += new ChangingEventhandler(instance_Changing);
instance.Value = "new string value";
//будет вызван метод instance_Changing
}
void instance_Changing(string oldValue)
{
Console.WriteLine(oldValue);
}
}
Anonymous methods greatly simplify life - the class structure remains clean, i.e. no need to create separate extra methods in the class itself.
Change the above example with binary operations using anonymous methods:
public class TestClass
{
public delegate int BinaryOp(int arg1, int arg2);
public void TestDelegates()
{
BinaryOp op = new BinaryOp(delegate(int a, int b)
{
return a + b;
});
int result = op(1, 2);
Console.WriteLine(result);
//выведет: 3
}
}
Isn't it shorter and cleaner?
Now consider lambda expressions .
An lambda expression is an anonymous function that contains expressions and operators, and can also be used to create delegates or an expression tree.
public class TestClass
{
public delegate int BinaryOp(int arg1, int arg2);
public void TestDelegates()
{
BinaryOp op = new BinaryOp((a, b) => a + b);
int result = op(1, 2);
Console.WriteLine(result);
//выведет: 3
}
}
But what will the event example look like? Very simple:
public class MyClass
{
/*
* весь остальной код
*/
public void TestEvent()
{
MyClass instance = new MyClass();
instance.Changing += (o) => Console.WriteLine(o);
instance.Value = "new string value";
//будет вызван Console.WriteLine
}
}
Well, we have reduced the code even more and it already becomes like a functional style (!!!). Yes, C # is also a functional language, as functions are first class objects.
Lambda expressions, as well as expression trees, were created with LINQ (Language Integrated Query).
Still not familiar with LINQ? Want to see what the famous MapReduce will look like on LINQ?
public static class MyClass
{
public void MapReduceTest()
{
var words = new[] {"...some text goes here..."};
var wordOccurrences = words
.GroupBy(w => w)
.Select(intermediate => new
{
Word = intermediate.Key,
Frequency = intermediate.Sum(w => 1)
})
.Where(w => w.Frequency > 10)
.OrderBy(w => w.Frequency);
}
}
Or use the SQL-like syntax:
public void MapReduceTest()
{
string[] words = new string[]
{
"...some text goes here..."
};
var wordOccurrences =
from w in words
group w by w
into intermediate
select new
{
Word = intermediate.Key,
Frequency = intermediate.Sum((string w) => 1)
}
into w
where w.Frequency > 10
orderby w.Frequency
select w;
}
In this example, we see both LINQ (GroupBy (). Select (). Where (), etc.) and anonymous classes -
new
{
Word = intermediate.Key,
Frequency = intermediate.Sum(w => 1)
}
Hmm ... what else is used here? The answer is simple - a powerful type inference system.
The main role here is played by the var keyword . C ++ 11 has a similar auto construct .
So without type inference, we would have to write like this:
public void MapReduceTest()
{
string[] words = new string[] { "...some text goes here..." };
var wordOccurrences = Enumerable.OrderBy(Enumerable.Where(Enumerable.Select(Enumerable.GroupBy(words, delegate (string w) {
return w;
}), delegate (IGrouping intermediate) {
return new { Word = intermediate.Key, Frequency = Enumerable.Sum(intermediate, (Func) (w => 1)) };
}), delegate (<>f__AnonymousType0 w) {
return w.Frequency > 10;
}), delegate (<>f__AnonymousType0 w) {
return w.Frequency;
});
}
[The compiler generated this method for us]
To describe all the other features of C #, its compiler settings, etc. more than one article needs to be devoted.
Meanwhile, C # 5 - which is already available and will be officially released soon - adds asynchronous programming , which also appeared in C ++ 11!
▌ Conclusion
C # and Java are powerful languages as well as powerful platforms (.NET and Java). As I wrote at the beginning of the article, there is a tool for each task.
C # - is not a continuation or copying of Java. Even when it was developed at Microsoft, its code name was COOL (C-like Object Oriented Language). How many times has this article cited an analogy with C / C ++? A sufficient amount.
I hope that my article helped to solve (at least a little) the issue of the difference between languages and platforms.
Thanks for attention!