Jump to content

Comparison of C Sharp and Java

From Alfawiki
C# and Java
Company typeComparative study
IndustryComputer programming languages

This article provides a comparative analysis of two programming languages: C# and Java. The evaluation emphasizes language characteristics while also addressing differences in platforms and libraries.

Both C# and Java share similarities, being statically and strongly typed languages that utilize a form of semi-interpretation or runtime just-in-time compilation. They follow an object-oriented paradigm and employ curly brace syntax akin to languages like C and C++.

Types

Data types Java C#
Arbitrary-size decimals Reference type; no operators Requires external library
Arbitrary-size integers Reference type; no operators Yes
Arrays Yes Yes
Boolean type Yes Yes
Character Yes Yes
Complex numbers Requires external library Yes
Date/time Yes; reference type Yes; value type
Enumerated types Yes; reference type Yes; scalar-based
High-precision decimal number No; see 'Arbitrary-size decimals' above 128-bit (28 digits) decimal type
IEEE 754 binary32 floating point number Yes Yes
IEEE 754 binary64 floating point number Yes Yes
Lifted (nullable) types No; only wrapper classes available Yes
Pointers No; limited to method references only Yes; accessible in unsafe mode or through IntPtr
Reference types Yes Yes
Signed integers 8, 16, 32, 64 bits 8, 16, 32, 64 bits
Strings Immutable reference type, Unicode support Immutable reference type, Unicode support
Type annotations Yes Yes
Single-root (unified) type system No; uses wrapper classes instead Yes; unified by default except in unsafe mode
Tuples No; limited third-party implementations Yes
Unsigned integers No; partial method support only 8, 16, 32, 64 bits
Value types No; restricted to primitive types only Yes

Unified type system

Both languages employ static typing with class-based object-oriented design. In Java, the set of primitive types exists separately from object-oriented reference types, lacking a shared base class. Conversely, C# implements a unified type system, where all types—excluding unsafe pointers—inherit from a single root object. This design ensures that every type supports the root's methods, enabling extension methods on the root to be applicable across all types, including primitives and delegates. Unlike Java, C# allows encapsulated objects that do not necessarily function as reference types.

In Java, complex types are equivalent to reference types, requiring methods to operate on instances of class reference types. C#, however, separates the concepts of encapsulation and method implementation from reference semantics, permitting methods on non-reference data structures. Still, only reference types in C# can utilize features like virtual methods and inheritance-based specialization.

Both languages provide numerous built-in data types that are passed by value. Java refers to these as primitive types, whereas C# labels them as simple types, which are generally supported directly by processor hardware.

C# simple types implement several interfaces, offering many instance methods even for literal values. Their names serve as aliases for underlying Common Language Runtime (CLR) types—for example, the CLR defines , while C# uses it as an alias.

Java does not allow methods directly on primitive values; instead, it relies on companion wrapper classes to provide operations. For example, the Java wrapper class encapsulates the primitive , though the two remain distinct entities.

Signed integers

Java and C# both provide support for signed integers in 8, 16, 32, and 64-bit sizes, using largely comparable naming schemes. The primary distinction lies in the designation of the 8-bit integer, which is named in Java and in C#.

Unsigned integers

C# includes both signed and unsigned integer variants, such as , , , and , corresponding to 8, 16, 32, and 64 bits respectively. Arithmetic operations on unsigned types produce unsigned outcomes, preserving type integrity.

In contrast, Java does not natively offer unsigned integer types, notably lacking an unsigned . The type undergoes sign extension, a behavior that frequently leads to errors and developer confusion. This approach was deliberate; James Gosling considered unsigned arithmetic to be commonly misunderstood by programmers. Java versions 8 and 9 introduced limited support for unsigned operations, accessible only through static methods on wrapper classes that treat signed primitives as unsigned values.

High-precision decimal numbers

C# provides a high-precision decimal datatype capable of representing numbers with up to 28 digits accurately, making it well-suited for financial calculations. Unlike binary floating-point types such as or , this decimal type can precisely represent decimal fractions like 0.1, minimizing rounding errors.

Java does not have a built-in decimal type but offers an arbitrary precision decimal class within its standard library. This class is a reference type manipulated via method calls rather than arithmetic operators, which restricts its ease of use compared to C#’s intrinsic decimal type.

Advanced numeric types

Both languages supply library-based arbitrary-precision arithmetic types to handle integers and decimals of arbitrary size.

Java uniquely provides a data type dedicated to arbitrary precision decimal computations, whereas C# exclusively supports a type designed for complex number representations.

Operations available on these advanced numeric types are more limited than those for built-in IEEE 754 floating-point types; for instance, functions like square root or logarithm are not supported.

C# allows seamless incorporation of library-defined numeric types with existing language constructs through custom implicit or explicit conversions and operator overloading. For more details, see Integration of library-defined types.

Characters

Both Java and C# feature a native datatype as a primitive type. When subjected to bitwise operations, values of this type are promoted to integers, resulting in numeric outputs rather than character values.

Built-in compound data types

In both languages, strings are immutable reference objects equipped with comprehensive methods for manipulation, parsing, and formatting tasks. Support for Regular expressions is provided externally via dedicated classes.

Each language offers classes to handle dates, times, time zones, and calendar systems accommodating various cultures. Java includes the mutable class with millisecond resolution and from Java 8 onward the package featuring immutable types such as , , and , which provide nanosecond precision.

C#’s structure is immutable and supports time measurements with 100-nanosecond accuracy; .NET 6 introduced the and structs for operations involving date-only and time-only values. Additionally, C# defines the type for representing durations; similarly, Java 8 supplies the . Both ecosystems enable date/time calculations across different cultures and time zones.

User-defined value types (structs)

In C#, developers can define custom value types using the keyword. Unlike classes, these value types are copied and assigned by value rather than by reference. They may exist as fields within objects, be boxed, or reside in arrays without the usual memory indirection associated with class instances.

Value types do not support inheritance and come with an implicit default constructor that initializes their memory to zero. Programmers have the option to create additional constructors that accept parameters. Due to the absence of a virtual method table and a fixed memory footprint, value types are inherently sealed but can implement interfaces, similar to built-in integer types.

Java does not offer native support for user-defined value types beyond its primitive data types.

Enumerations

While both languages provide enumeration support, their implementations vary considerably, posing challenges for automated translation tools such as Java-to-C# converters.

C# enums resemble those in C: they act as wrappers around integral bit-flags (such as int, byte, short). This design yields performance advantages and facilitates interoperability with C/C++ code but restricts features and introduces potential errors due to direct casting between enumeration values and their underlying integral types. Consequently, C# enumerations primarily serve as syntactic sugar over integral values.

Conversely, Java treats enumerations as fully-fledged classes where each enum constant is an object instance. Java enums can include fields, methods, and implement interfaces, offering richer functionality at the cost of increased memory use and reduced compatibility with C/C++ code. The comparison below highlights key distinctions:

Java C#
Definition Enumerations are classes; enum constants are instances that may have unique fields or methods and implement interfaces. Only the declared constants are valid values. Enumerations inherit from the Enum value type; constants correspond to named integral values (ranging from 8 to 64 bits). By default, values start at zero and increment by one; any integral value can be explicitly cast to an enum.
Combining values Specialized enum collections allow efficient combination of multiple enum constants. Supports bit-field enums enabling bitwise OR operations; parsing and formatting handle combined flags appropriately.

Both languages permit using enumerations within switch statements directly without converting them to strings or integers. However, C# prohibits implicit fall-through between cases unless the case block is empty, thereby reducing common errors; explicit fall-through requires a goto statement.

Delegates and method references

C# utilizes delegates to represent object-oriented method references—specialized types that encapsulate pointers to methods. These delegates can be assigned to variables or passed as parameters for deferred invocation. They support covariance and contravariance and can reference any static method, instance method, anonymous method, or lambda expression matching the delegate signature.

Although related to closures and inline functions, delegates differ in that closures must be captured by a delegate for later execution. Delegates also form the foundation of C# events, though they remain distinct concepts.

Java omits delegates entirely, considering them unnecessary and potentially detrimental to performance. Instead, it employs approaches such as the wrapper pattern, granting client access to methods through interfaces, and adapter objects implemented via inner classes—viewed as superior alternatives to bound method references.

See also C# delegates and equivalent Java constructs.

Lifted (nullable) types

In C#, value types can be "lifted" to accept the special value by appending a suffix or utilizing the generic type. Implicit conversions are available between the original and lifted versions. These lifted types facilitate comparisons with , support testing using , and include lifted operators that propagate null values—except for certain Boolean operators, which employ ternary logic compatible with SQL standards.

Java does not implement type lifting but instead uses wrapper classes for primitive types, allowing them to behave as reference types that can hold null. Attempting to dereference a null wrapper results in a runtime , including during automatic unboxing, which leads to exceptions if the value is null.

To summarize, C#’s lifted operators handle null propagation safely, whereas Java’s unboxing mechanism throws exceptions when encountering nulls. Moreover, Java’s Boolean operators lack the ternary logic support found in C#.

Late-bound (dynamic) type

C# provides a late-bound dynamic type that enables dynamic invocation without relying on reflection, facilitates interoperability with dynamic languages, and supports flexible binding to objects such as document models. The type defers member resolution until runtime rather than compile time, with reflection-based member lookup serving as an extendable fallback.

Important business applications of the type in C# include:

  • Streamlining reflection by permitting direct access to properties, methods, and events without explicit reflection calls.
  • Supporting dynamic language interoperability through a hub-and-spoke architecture for efficient member resolution.
  • Allowing on-the-fly creation of dynamic abstractions, such as simplified interaction with XML or XHTML document object models.

Java does not have an equivalent late-bound type and addresses these scenarios differently:

  • Dynamic invocation is achieved via reflection.
  • Interoperability with dynamic languages is handled through language-specific APIs without a unified standard.
  • Runtime object creation and manipulation rely on dedicated abstraction APIs.

See also #Interoperability with dynamic languages.

Pointers

Java prohibits pointers and pointer arithmetic within its runtime environment to improve safety and prevent common errors. Instead, it uses the Java Native Interface (JNI) as an external bridge for interacting with the operating system, thus avoiding direct memory layout manipulation.

C# allows pointers and pointer arithmetic but only within explicitly marked unsafe code blocks using the keyword and requires compilation with the option. Assemblies compiled under these conditions are designated unsafe and run only when explicitly trusted. This design permits direct native memory access for interoperability purposes while confining potentially hazardous operations to trusted code segments.

Reference types

Both languages treat references as fundamental, passing all class instances by reference. Each supports weak references, enabling objects referenced only weakly to be collected by the garbage collector. This functionality is accessible through libraries but is integrated into the runtime environment. Java offers additional soft references, which postpone object reclamation until memory pressure arises—a capability not present in C#.

Reference types Java C#
Garbage collection Yes Yes
Weak references Yes Yes
Reference queue Yes Yes
Soft references Yes No
Phantom references Yes No
Proxy support Yes; proxy generation Yes; object contexts

Arrays and collections

Both Java and C# extensively utilize arrays and collection types.

Arrays and Collections Java C#
Abstract data types yes yes
One-dimensional, zero-based arrays yes yes
Rectangular multidimensional arrays no yes
Jagged arrays (arrays of arrays) yes yes
Non-zero based arrays no some
Unified arrays and collections no yes
Maps/dictionaries yes yes
Sorted dictionaries yes yes
Sets and sorted sets yes yes
Lists/vectors, queues/stacks, priority queues yes yes
Bags/multisets no (third-party) yes
Concurrency-optimized collections yes yes

While C# supports rectangular multidimensional arrays, Java relies solely on jagged arrays. In C#, arrays derive from the System.Array class and implement collection interfaces, allowing them to be used wherever sequences or collections are expected. Conversely, Java arrays are distinct from its collection framework and need to be wrapped to achieve similar behavior.

Both languages provide streamlined iteration over arrays through enhanced for-loops ('for-each' in Java and 'foreach' in C#). Arrays in both languages are covariant with respect to reference types but enforce runtime type safety checks during element assignment.

Java’s collections framework offers a broad set of algorithms for sorting, searching, and manipulating data structures. C# provides similar functionality through the System.Collections and System.Collections.Generic namespaces, which include generic implementations as well as thread-safe wrappers. The addition of LINQ in .NET 3.5 further enhances querying capabilities across various collection types.

Using multidimensional arrays can improve performance by optimizing memory locality; however, they involve more complex indexing. Java simplifies jagged array creation through syntax supported by the JVM, whereas C# allows allocation of rectangular arrays in a single operation.

Both languages supply comprehensive collection categories—such as ordered and unordered lists, maps/dictionaries, and sets—effectively addressing diverse business requirements.

Tuples

C# supports tuple types (product types) via two distinct approaches. The first involves tuple classes—immutable reference types introduced in .NET Framework 4.0—which have largely been supplanted by tuple structs, mutable value types available since .NET Framework 4.7.

The primary distinctions are that tuple structs are value types with lower memory overhead and expose mutable fields, unlike the immutable properties found in tuple classes. Starting with C# 7.0, native language features enable direct construction, deconstruction, and manipulation of tuple instances, including customizable field names—unlike the fixed element names such as Item1, Item2 in classes.

Java does not include built-in tuple support; instead, similar functionality is offered through third-party libraries that resemble C#’s immutable tuple classes. Compared to C#’s native tuples and their syntax conveniences, Java’s tuples require explicit constructors or factory methods with fixed element identifiers, making them less straightforward to use.

Expressions and operators

Expressions and operators Java C#
Arithmetic operations Yes Yes
Logical operations Yes Yes
Bitwise operators Yes Yes
Conditional expressions Yes Yes
String concatenation Yes Yes
String interpolation Yes; Available from Java 21 Yes
Verbatim (here-) strings Yes Yes
Type casting Yes Yes
Boxing Yes; implicit conversion Yes; implicit conversion
Unboxing Yes; implicit conversion Yes; explicit conversion
Lifted operators No; See java.util.Optional Yes
Overflow detection/control No Yes
Strict floating-point evaluation Yes; optional opt-in/out feature Yes; opt-in only

Boxing and unboxing

Both C# and Java provide support for automatic boxing and unboxing, facilitating implicit conversions between primitive data types and their associated reference wrapper classes.

In C#, primitive data types are subtypes of the Object class, which permits invoking methods such as overrides of Object's directly on primitives. Conversely, Java maintains a clear separation between primitive types and their wrapper classes, connected solely through autoboxing and unboxing mechanisms to preserve legacy compatibility. This design requires explicit casting in Java to call methods on boxed primitives—for instance, compared to C#'s more direct approach with .

Moreover, Java extensively employs boxed types within generics (refer to #Generics below).

Statements

Statements Java C#
Loop constructs Yes Yes
Conditional statements Yes Yes
Control flow mechanisms Yes Yes
Assignment statements Yes Yes
Exception handling control Yes Yes
Declaration of variables Yes Yes
Type inference for variables Yes[1] Yes
Deterministic resource disposal (ARM-blocks) Yes Yes

Syntax

Both languages belong to the "curly brace" family derived from C/C++, sharing very similar syntax for statements and expressions. Variations primarily emerge in type declarations: Java explicitly specifies class inheritance and interface implementation, whereas C# derives these relationships implicitly through its inheritance model.

C# encompasses a wider array of language features, reflected in a more extensive set of keywords and syntactic rules compared to Java.

Keywords and backward compatibility

Java and C# have adopted distinct strategies for introducing new keywords while preserving backward compatibility.

Java tends to avoid adding new keywords whenever possible, preferring to expand existing syntax or repurpose keywords in novel contexts. For example, the loop was extended to support iterable objects, while and were redefined for generics starting with Java 1.5. The addition of in Java 1.4 posed potential conflicts with existing identifiers; this was mitigated through compiler options that defaulted to earlier language versions to maintain compatibility while issuing warnings.

In contrast, C# introduced new keywords as contextual, enabling them to coexist alongside identifiers without causing conflicts. Keywords such as and introduced in C# 2.0 do not disrupt source compatibility, allowing the language to evolve smoothly without requiring explicit version declarations.

Keyword Feature or usage example
checked, unchecked Enable runtime detection of arithmetic overflow conditions in C#.
get, set Support for properties with optional getter/setter accessors in C#, unlike Java which relies on explicit accessor methods.
goto Present in C# for control flow scenarios such as finite-state machine implementations or generated code. Java reserves the keyword but does not implement it, instead using labeled versions of break and continue.

<syntaxhighlight lang="csharp"> switch (color) {

   case Color.Blue:
       Console.WriteLine("Color is blue");
       break;
   case Color.DarkBlue:
       Console.WriteLine("Color is dark");
       goto case Color.Blue;

} </syntaxhighlight>

lock In C#, provides thread synchronization by locking code blocks using a monitor pattern implemented via a try ... finally construct.
out, ref Allow output parameters and pass-by-reference semantics in C#, enabling functions to return multiple values or modify arguments directly.
strictfp A Java keyword enforcing consistent floating-point computations across different platforms.
switch The C# switch statement supports strings and long integers with controlled fallthrough via 'goto case'. Java's switch supports strings since version 7 but not long integers; it permits fallthrough except where explicitly terminated by 'break'.
synchronized A Java keyword used for thread synchronization analogous to C#'s lock keyword, implemented through monitor locking.
throws In Java, methods must declare checked exceptions they might throw; this concept has no direct equivalent syntax in C#.

<syntaxhighlight lang=Java> public int readItem() throws java.io.IOException {

   // ...

} </syntaxhighlight>

using Ensures disposal of resources by calling an object's Dispose method upon block exit or exception in C#, relying on the IDisposable interface.

<syntaxhighlight lang="csharp"> // Write to "test.txt" safely using (var file = new StreamWriter("test.txt")) {

   file.Write("test");

} </syntaxhighlight> Java SE 7 introduced a similar construct with try-with-resources: <syntaxhighlight lang=Java> try (BufferedReader br = new BufferedReader(new FileReader(path))) {

   return br.readLine();

} </syntaxhighlight>


Object-oriented programming

Both Java and C# are crafted as object-oriented languages that utilize dynamic dispatch, featuring syntax reminiscent of C++ (which itself is derived from C). Neither language serves as a superset of C or C++.

Object orientation Java C#
Classes mandatory mandatory
Interfaces Yes Yes
Abstract classes Yes Yes
Member accessibility levels public, package, protected, private public, internal, protected, private, protected internal
Class-level inner classes Yes Yes
Instance-level inner classes Yes No
Statement-level (local) anonymous classes Yes without methods
Partial classes No; third-party library Yes
Implicit (inferred) anonymous classes No Yes
Deprecation/obsolescence Yes Yes
Overload versioning Template:Some Yes
Enums implementing interfaces Yes No
Properties No Yes
Events via standard libraries built-in feature
Operator overloading No Yes
Indexers No Yes
Implicit conversions No; see autoboxing Yes
Explicit conversions Yes Yes

Partial class

The concept of partial classes is supported in C#, enabling a single class to be divided among several source files designated with the keyword . These segments are compiled collectively, allowing mutual references, interface implementations, and base class declarations to reside within any part. This capability is particularly useful for code generation contexts such as user interface development by isolating generated code from manual modifications, thereby avoiding accidental overwrites. Unlike extension methods, partial classes permit circular dependencies that are resolved during compilation. This feature is absent in Java.

Inner and local classes

Both Java and C# accommodate inner classes, defined as classes nested within other classes, though their operational behavior varies.

In Java, instances of non-static inner classes retain a link to their enclosing outer class instance, which provides access to both static and instance members of the outer class. To instantiate such an inner class, the syntax requires referencing the outer class instance explicitly.

Conversely, in C#, inner classes function similarly to standard classes with the enclosing class primarily serving as a namespace container. Inner classes lack automatic access to non-static members of the outer class unless explicitly supplied with an instance reference. Additionally, these inner classes can be declared private to limit visibility within the outer class.

Java also includes local or anonymous classes defined inside method bodies, commonly used for implementing interfaces with minimal methods—such as event handlers—or for overriding superclass methods. These local classes have access to final local variables from their enclosing scope. In contrast, C# fulfills comparable requirements through anonymous delegates; refer to event handling for further details.

Moreover, C# introduces anonymous types, which allow creating objects by specifying property names alongside initialization expressions with inferred types. These anonymous types derive directly from the base object class.

Event

C# employs multicast delegates combined with events to facilitate event-driven programming and realize the observer pattern. It provides dedicated syntax for declaring events along with operators designed to add or remove event handlers.

In contrast, Java handles events through a different mechanism; refer to this section for further explanation.

Operator overloading and conversions

C# allows both Operator overloading and user-defined casting, which enables custom types such as and to work naturally with built-in operators like addition and multiplication. The language limits overloading for specific operators (for example, , , , ), but compound assignment operators internally invoke their respective simple operator overloads.

Java deliberately omits support for operator overloading and custom conversions to preserve language simplicity and avoid potential misuse.

Indexer

C# introduces indexers, a variant of operator overloading akin to C++'s , permitting objects to be accessed using array-like syntax. An indexer acts as a property named that accepts parameters of various types:

<syntaxhighlight lang="csharp"> myList[4] = 5; string name = xmlNode.Attributes["name"]; orders = customerMap[theCustomer]; </syntaxhighlight>

Java does not provide indexers; instead, it relies on explicit getter and setter methods where C# would use indexers.

Fields and initialization

Fields and initialization Java C#
Fields Yes Yes
Constants Yes Yes; but lacks constant parameters support
Static constructors Yes Yes
Instance constructors Yes Yes
Finalizers/destructors Yes Yes
Instance initializers Yes No; achievable through constructors
Object initialization Bottom-up
(fields and constructors)
Top-down (fields); bottom-up (constructors)
Object initializers Yes Yes
Collection initializers Static varargs methods Yes
Array initializers Yes Yes

Object initialization

Both Java and C# initialize object fields either via variable initializers or constructor routines. Java additionally supports instance initializers—anonymous code blocks executed after superclass constructors but before the constructor body itself.

The sequence of object field initialization in C# is:

  1. Static fields of the derived class
  2. Static constructor of the derived class
  3. Instance fields of the derived class
  4. Static fields of the base class
  5. Static constructor of the base class
  6. Instance fields of the base class
  7. Base class instance constructor
  8. Derived class instance constructor

Fields declared in the object's own class are referred to as "derived," while those from superclasses are "base." All fields, including private ones from base classes, reside within the object's memory layout. Field initializers execute prior to any constructor logic. A significant caution is invoking virtual methods during base class construction since overridden implementations in subclasses may access fields that have not yet been initialized.

Java performs object initialization as follows:

  1. Execution of another constructor (either in the same or superclass)
  2. Initialization of instance variables and execution of instance initializer blocks in source code order
  3. Execution of the constructor body

Constructors may explicitly invoke other constructors; if not, the no-argument superclass constructor is called implicitly. Superclass variables are initialized before subclass variables. Variable initializations occur sequentially according to their textual declaration order.

Potential pitfalls in Java include: - Variable initializers calling methods that reference variables declared later, which remain at default values during initialization. - Overridden methods invoked from base constructors accessing subclass fields that have not yet been initialized.

This latter concern also exists in C#, although it is somewhat mitigated because methods are not overridable by default.

Resource disposal

Both Java and C# rely mainly on garbage collection to free up memory, instead of manual deallocation. When dealing with resources beyond memory—such as file handles or graphical assets—explicit signaling is necessary once the program finishes using them. Each language offers interfaces to support deterministic disposal, and starting with Java 7, both have introduced automatic resource management constructs that automatically trigger disposal or closing methods.

Methods

Methods and properties Java C#
Static imports Yes Yes
Virtual methods Virtual by default Non-Virtual by default
Abstract Yes Yes
Sealing Yes Yes
Explicit interface implementation Default methods Yes
Value (input) parameters Yes Yes
Reference (input/output) parameters No Yes
Output (output) parameters No Yes
Constant (immutable) parameters Yes; final parameters Yes
Variadic methods Yes Yes
Optional arguments No; uses method overloading or varargs Yes
Named arguments Yes; with annotations Yes
Generator methods No; see Stream API Yes
Extension/default methods Yes Yes
Conditional methods No Yes
Partial methods No Yes

Extension methods and default methods

In C#, a static method can be transformed syntactically into a member method of the first parameter’s type by using the special this keyword. These extension methods must reside within a static class and comply with member access rules, preserving encapsulation. They become available only when the namespace of the containing class is imported.

Java introduced default methods in interfaces starting with version 8, enabling method implementations directly inside interfaces. Contrary to C# extension methods, these default methods are instance-level within the interface. Implementing classes can override these defaults by providing their own implementations.

Both languages permit overriding these extended or default methods through corresponding definitions in implementing classes. In C#, class-defined methods override extension methods; in Java, classes inherit the default interface implementations unless they explicitly override them.

Partial methods

C# offers partial methods within partial classes, which allow method declarations under strict signature constraints. If no implementation is supplied anywhere in the partial class, both the method declaration and all invocations are omitted during compilation, resulting in zero runtime overhead. This feature supports multiple interception points similar to the template method pattern without incurring performance penalties if unused. Java lacks an equivalent capability.

Virtual methods

In C#, methods are non-virtual by default and must be explicitly marked with the virtual keyword to enable overriding. Conversely, in Java, all instance methods that are neither static nor private are virtual automatically. Virtual dispatch guarantees that the most derived override of a method is invoked at runtime, but this comes with performance costs since such calls generally cannot be inlined and rely on an indirect call through the virtual method table. Some Java Virtual Machine implementations, including Oracle's, mitigate this overhead by inlining frequently invoked virtual methods.

Java allows methods to be sealed with the final modifier to prohibit further overriding. Unlike C#, Java does not permit subclasses to declare new unrelated methods sharing the same name as those in base classes.

By default, Java permits derived classes to override base class methods implicitly. In C#, this behavior occurs only when explicitly enabled; otherwise, new methods with identical names and signatures can be defined in derived classes. When invoking through a superclass reference, the most specific overridden implementation corresponding to the actual subclass instance is executed.

If a subclass declares a method matching a base class method's signature, Java automatically treats it as an override, which can sometimes be unintended. In contrast, C# requires the use of the override keyword to explicitly indicate overriding; if omitted, the new method hides the base one and triggers a compiler warning unless suppressed via the new keyword. This mechanism helps avoid accidental overrides caused by base class modifications. Java provides an optional @Override annotation for clarity, but its use is not enforced and omitting it does not produce warnings.

Constant/immutable parameters

The Java final keyword prevents reassignment of local variables or method parameters. For primitive types, this enforces immutability; for reference types, it prohibits reassigning the reference itself but does not prevent modifications to the object's internal state. Since C# 7.2, the in modifier similarly disallows reassignment of method parameters but cannot be applied to local variables. Like Java's final, C#'s in restricts reassignment while still permitting mutation of referenced objects.

Java C#
<syntaxhighlight lang="java">

public int addOne(final int x) {

   x++; // ERROR: final variable cannot be reassigned
   return x;

}

public List addOne(final List<Integer> list) {

   list.add(1); // ALLOWED: final reference can still mutate object state
   return list;

}</syntaxhighlight>

<syntaxhighlight lang="csharp">

public int AddOne(in int x) {

   x++; // ERROR: readonly parameter cannot be reassigned
   return x;

}

public List<int> AddOne(in List<int> list) {

   list.Add(1); // ALLOWED: readonly reference can still modify object contents
   return list;

} </syntaxhighlight>

Neither language fully supports const-correctness as seen in C or C++, where methods themselves can be declared constant.

In Java, constants are typically represented by fields declared as static final, often written in uppercase letters with underscores by convention, though this naming style is not enforced by the language. A parameter marked only as final is not inherently constant unless it refers to an immutable primitive type or an immutable class such as String.

Generator methods

In C#, any method returning IEnumerable, IEnumerator, or their generic counterparts may utilize the yield syntax. This compiler feature automatically generates state machines that simplify producing sequences lazily, including infinite series like Fibonacci numbers.

Java does not offer a direct equivalent; instead, generator-like behavior is achieved by implementing collection or iterable interfaces that compute elements on demand. To support iteration within for-each loops, such generators must implement the Iterable interface.

Refer to the Fibonacci sequence example below for illustration.

Explicit interface implementation

C# offers the capability of explicit interface implementation, which permits a class to implement interface methods separately from its own members or to provide unique implementations for identically named methods originating from multiple interfaces.

When several interfaces declare methods sharing the same signature, C# typically uses a single shared implementation; however, explicit interface implementation allows different behaviors based on the interface through which the object is accessed. In contrast, Java does not provide this functionality and necessitates modifying interfaces to resolve naming conflicts.

Reference (in/out) parameters

In Java, primitive data types are passed by value, whereas objects are passed by reference, enabling methods to alter the state of objects but not directly modify primitive variables. For instance, String instances remain immutable, while mutable objects can be changed.

C# enables explicit pass-by-reference semantics using the keyword, which supports methods returning multiple values without additional wrappers. Java lacks built-in mechanisms for returning multiple values without using helper classes such as "Ref".

Java C#
<syntaxhighlight lang="java" style="font-size:95%">

class PassByRefTest {

   static class Ref<T> {
       T val;
       Ref(T val) { this.val = val; }
   }
   
   static void changeMe(Ref<String> s) {
       s.val = "Changed";
   }
   static void swap(Ref<Integer> x, Ref<Integer> y) {
       int temp = x.val;
       x.val = y.val;
       y.val = temp;
   }
   public static void main(String[] args) {
       var a = new Ref(5);
       var b = new Ref(10);
       var s = new Ref("still unchanged");
       
       swap(a, b);
       changeMe(s);
       System.out.println( "a = " + a.val + ", " +
                           "b = " + b.val + ", " +
                           "s = " + s.val );
   }

} </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:95%">

class PassByRefTest {

   public static void ChangeMe(out string s) 
   {
       s = "Changed";
   }
   public static void Swap(ref int x, ref int y) 
   {
       int temp = x;
       x = y;
       y = temp;
   }
   public static void Main(string[] args) 
   {
       int a = 5;
       int b = 10;
       string s = "still unchanged";
       Swap(ref a, ref b);
       ChangeMe(out s);
       System.Console.WriteLine("a = " + a + ", " +
                                "b = " + b + ", " +
                                "s = " + s);
   }

}</syntaxhighlight>

<syntaxhighlight lang="text" class="" style="" inline="1">a = 10, b = 5, s = Changed</syntaxhighlight> <syntaxhighlight lang="text" class="" style="" inline="1">a = 10, b = 5, s = Changed</syntaxhighlight>

Exceptions

Exception Features Java C#
Checked exceptions Yes No
Try-catch-finally blocks Yes Yes
Exception filtering No Yes

Checked exceptions

Java enforces checked exceptions, requiring that methods declare or handle exceptions they might throw, thereby encouraging disciplined error management. Conversely, C# supports only unchecked exceptions and does not impose this obligation.

According to Anders Hejlsberg, the lead architect of C#, checked exceptions represent a Java experiment with limited applicability beyond straightforward cases. Critics contend that checked exceptions often result in empty catch blocks (e.g., ) that suppress errors silently rather than allowing them to propagate. Employing exception chaining—wrapping and re-throwing exceptions—can preserve abstraction boundaries, such as converting a into an when changing data sources.

Advocates like James Gosling maintain that checked exceptions compel explicit error handling, which reduces unnoticed failures. Although it is possible to ignore exceptions intentionally in Java, doing so requires conscious coding decisions; unchecked exceptions allow omission by default without such explicit intent.

Try-catch-finally

Handling of the try-catch-finally construct differs between the two languages. The finally block is guaranteed to run regardless of whether the try or catch blocks contain control-transfer statements such as return or throw. In Java, unexpected outcomes may arise if a catch block exits with a return value, but the subsequent finally block also terminates with a different return. C# prevents such conflicts by disallowing control-transfer commands like return or throw inside the finally block.

A primary business application of the finally block is managing resources, ensuring essential assets are properly released within this section. C# further simplifies this pattern through the using statement, which syntactically guarantees that an object's Dispose method is invoked.

Another nuanced distinction relates to when a stack trace is captured during exception throwing. In Java, the stack trace is recorded at the moment the exception object is created:

<syntaxhighlight lang=Java> class Foo {

   Exception up = new Exception();
   int foo() throws Exception {
       throw up;
   }

} </syntaxhighlight>

This means the exception always retains the stack trace from its construction time, irrespective of how many times the method foo is invoked. Conversely, C# generates the stack trace only when the throw statement executes:

<syntaxhighlight lang="csharp"> class Foo {

   Exception e = new Exception();
   int foo()
   {
       try
       {
           throw e;
       }
       catch (Exception e)
       {
           throw;
       }
   }

} </syntaxhighlight>

Here, the exception's stack trace corresponds to the initial throw location. When rethrowing exceptions in C#, using throw; preserves the original stack trace, whereas throwing with an exception variable (e.g., throw e;) creates a new stack trace.

Finally blocks

Java allows control flow to exit a finally block regardless of how it was entered, which can override other control flow statements mid-execution:

<syntaxhighlight lang="java"> int foo() {

   try {
       return 0;
   } finally {
       return 1;
   }

} </syntaxhighlight>

In this case, although the try block attempts to return 0, the finally block’s return statement supersedes it, causing the method to ultimately return 1.

In contrast, C# restricts premature exit from a finally block via control flow commands except for continue. Specifically, statements such as break, return (outside of finally), and loop controls like goto or break (if outside) are prohibited within a finally section.

Generics

While Java and C# share some syntactic similarities in their approach to generic programming, their underlying implementations differ significantly.

Generics Java C#
Implementation Uses Type erasure Employs Reification
Runtime realization Not supported Supported
Type variance Present via use-site annotations Available through declaration-site annotations (limited to interfaces)
Reference type constraint Implicitly enforced Explicitly supported
Value/primitive type constraint Not available Supported
Constructor constraint Absent Supported
(restricted to parameterless constructors)
Subtype constraint Supported Supported
Supertype constraint Supported Not supported
Migration compatibility Ensured Not ensured

Type erasure compared to reified generics

In Java, generics function solely during compilation and are implemented through type erasure, which substitutes generic types with raw types in the generated bytecode. This means that no generic type details persist at runtime. While this method maintains backward compatibility, it introduces constraints such as the inability to apply primitives as type arguments or to declare generic exceptions. Efforts to incorporate reified generics into Java continue under the scope of Project Valhalla.

In contrast, C# integrates generics directly into the CLR, enabling runtime support by means of reification. The CLR produces specific class implementations for each distinct generic instantiation during execution, facilitating compile-time type safety without the need for runtime casting. This design allows primitives to be used as generic parameters without incurring boxing costs and supports capabilities like generic exceptions and static members tailored to each generic instance.

The primary distinctions include:

Java C#
Ensures compile-time type safety without additional runtime verifications, enhancing performance.
Allows primitive and value types as generic parameters without boxing overhead.
Supports generic exceptions and their handling in catch clauses.
Static members are unique for each generic instantiation.
No limitations on employing type parameters within static contexts.
Fully supports arrays of generic instantiations and type parameters.
Generic instantiations correspond to actual classes with accessible class literals.
The is and as operators function normally with generics.
New instances can be created from type parameters when constructor constraints are specified.
Reflection fully preserves generic type data at runtime.

By leveraging runtime generics, C# achieves superior flexibility and efficiency in enterprise applications by eliminating boxing overhead and providing comprehensive reflection and instantiation support. Conversely, Java’s strategy focuses on preserving legacy compatibility but introduces performance drawbacks when managing primitives or requiring runtime type metadata.

To address performance issues related to boxing and unboxing in generics, some Java third-party libraries enhance collections by internally utilizing primitive arrays.

Migration compatibility

Java's implementation of type erasure was specifically engineered to facilitate migration compatibility, a concept distinct from backward compatibility. This approach was intended to enable a seamless transition for the Collections APIs introduced in Java 2, permitting new generic collection types to be used with methods originally designed for older collection classes.

In contrast, C# generics maintain complete backward compatibility but do not fully support migration compatibility. Legacy applications operate without modification on a generics-aware runtime environment, eliminating the need for recompilation. The introduction of new generic collection classes in .NET complemented rather than replaced the existing non-generic collections from version 1.x and implemented non-generic interfaces where feasible. Nevertheless, these generic collections cannot always be integrated directly with existing methods that depend on older collection classes.

Covariance and contravariance

Covariance and contravariance are features supported by both Java and C#. Java employs use-site variance, allowing a single generic class to declare members exhibiting both covariance and contravariance. Conversely, C# utilizes define-site variance applicable to generic interfaces and delegates; variance is not directly supported on classes but can be accessed through variant interfaces. Additionally, C# provides use-site covariance for methods and delegates.

Functional programming

Functional programming Java C#
Method references Yes Yes
Closures Template:Some Yes
Lambda expressions Yes Yes
Expression trees No Yes
Generic query language/API Yes; Java Stream API (Monad) Yes
Tail recursion compiler optimizations No Template:Some

Closures

A closure is an inline function that captures variables from its surrounding lexical environment.

C# offers support for closures through anonymous methods or lambda expressions that provide full closure capabilities.

Before Java 8, closures were typically simulated using anonymous inner classes. With the arrival of Java 8, lambda expressions were introduced that fully capture the current scope without generating a new one. Prior to this, only final variables from the enclosing context could be accessed, requiring explicit declaration and sometimes boxing.

When method references are passed for deferred execution, C# closures have access to all variables within their lexical scope, whereas Java's anonymous inner classes limit access strictly to final variables.

Lambdas and expression trees

Both Java and C# support lambda expressions—anonymous functions primarily used as local function arguments, an essential aspect of functional programming.

Uniquely, C# enables lambdas to form expression trees, which can represent executable code or data structures depending on compiler type inference. This feature is fundamental to frameworks such as Language Integrated Query (LINQ).

Metadata

Metadata Java C#
Annotations/attributes for metadata Interface-based; supports user-defined annotations Class-based
Positional parameters No; except when a single argument is present Yes
Named parameters Yes Yes
Default parameter values Declared at definition Set via initialization
Nested type support Yes Yes
Metadata specialization No Yes
Conditional metadata inclusion No Yes

Preprocessing, compilation and packaging

Preprocessing, Compilation and Packaging Java C#
Namespaces Packages Namespaces
Source file flexibility Limited Unrestricted
Packaging mechanism Package system Public/internal access modifiers on namespace members, compiled into modules and assemblies at CLR level
Class/assembly resolution path Template:Partial Available at both compile-time and runtime
Support for conditional compilation No; except via Apache Ant tools Yes
Custom error and warning handling Yes; AnnotationProcessor tool Yes
Explicit code regions No Yes

Namespaces and source file organization

In C#, namespaces operate similarly to those in C++, differing from Java’s package names which are conventionally linked to the directory structure of source files. Although Java typically arranges source files to correspond with package folders, this is not an absolute requirement.

Both languages allow importing classes (e.g., ), facilitating the use of simple class names in code. When identical class names exist across different namespaces or packages, fully qualified names or selective imports resolve ambiguity. Java supports importing individual classes (e.g., ), while C# permits aliasing imports (e.g., ) and importing specific generic type instantiations (e.g., ).

Each language includes static import capabilities to access static methods or fields without qualifying their class names. C# additionally provides static classes limited to static members and, starting with version 3.0, supports extension methods that add new methods to existing types.

Java enforces that source filenames must match their single public class, whereas C# allows multiple public classes within one file without filename constraints. Moreover, from C# 2.0 onward, classes can be declared across multiple files using the keyword, offering greater flexibility in separating logical code units compared to Java’s strict one-class-per-file convention.

Conditional compilation

In contrast to Java, C# utilizes preprocessor directives to enable conditional compilation and includes an Annotation that triggers method calls only when a specified compilation symbol is defined. This feature allows constructs like , which execute solely if the symbol is active. Since Java 1.4, assertions are built into the language but remain disabled by default; they can be activated at runtime using the or options for the JVM.

Threading and asynchronous capabilities

Both Java and C# incorporate thread synchronization constructs directly within their language features.

Threading and Synchronization Java C#
Threads yes yes
Thread pool yes yes
Task-based parallelism yes yes
Semaphores yes yes
Monitors yes yes
Thread-local variables yes yes; using ThreadStaticAttribute and the ThreadLocal<T> class

Task-based parallelism in C#

With the release of .NET Framework 4.0, a task-oriented programming approach was introduced, focusing on the Task and Task<TResult> classes, which replaced the older event-driven asynchronous model. Tasks can be combined and chained, with asynchronous methods typically ending with the Async suffix.

<syntaxhighlight lang="csharp"> public static class SomeAsyncCode {

   public static Task<XDocument> GetContentAsync()
   {
       HttpClient httpClient = new HttpClient();
       return httpClient.GetStringAsync("www.contoso.com").ContinueWith((task) => {
           string responseBodyAsText = task.Result;
           return XDocument.Parse(responseBodyAsText);
       });
   }

}

var t = SomeAsyncCode.GetContentAsync().ContinueWith((task) => {

   var xmlDocument = task.Result;

});

t.Start();

</syntaxhighlight>

The introduction of C# 5 brought language-level support for asynchronous programming through the async modifier and the await keyword, enabling asynchronous code to be written in a more straightforward, synchronous-like manner.

<syntaxhighlight lang="csharp"> public static class SomeAsyncCode {

   public static async Task<XDocument> GetContentAsync()
   {
       HttpClient httpClient = new HttpClient();
       string responseBodyAsText = await httpClient.GetStringAsync("www.contoso.com");
       return XDocument.Parse(responseBodyAsText);
   }

}

var xmlDocument = await SomeAsyncCode.GetContentAsync();

// The Task starts automatically with await.

</syntaxhighlight>

These enhancements internally create a state machine to manage continuations automatically, simplifying asynchronous programming in C#.

Task-based parallelism in Java

Since JDK 1.0, Java has supported threads, allowing flexible task execution through the functional interface Runnable, which contains a single void method without parameters. For instance: <syntaxhighlight lang="Java" line> var myThread = new Thread(() -> {

   var threadName = Thread.currentThread().getName();
   System.out.println("Hello " + threadName);

});

myThread.start(); </syntaxhighlight>

Java also offers advanced concurrency utilities via the ExecutorService, which manages asynchronous tasks and groups of threads. Tasks submitted to an executor run within a thread pool, enabling reuse of threads for multiple concurrent executions during an application's runtime.

The previous example adapted to use an executor: <syntaxhighlight lang="Java" line> ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(() -> {

   var threadName = Thread.currentThread().getName();
   System.out.println("Hello " + threadName);

}); </syntaxhighlight>

The ExecutorService supports the Callable interface, similar to Runnable but capable of returning a result. This allows asynchronous operations that produce values, such as retrieving web content: <syntaxhighlight lang="Java" line> ExecutorService executor = Executors.newSingleThreadExecutor();

Future<String> contentAsync = executor.submit(() -> {

   HttpRequest httpReq = HttpRequest.newBuilder()
       .uri(new URI("www.graalvm.org"))
       .build();
           
   return HttpClient.newHttpClient()
       .send(httpReq, BodyHandlers.ofString())
       .body();

});

var webPageResult = contentAsync.get(); </syntaxhighlight> Invoking get() blocks execution until the asynchronous operation completes and returns its result.

To emulate C#-style async methods, Java can encapsulate asynchronous logic within classes and methods: <syntaxhighlight lang="Java" line> public static class SomeAsyncCode {

   static ExecutorService executor = Executors.newSingleThreadExecutor();
   public static Future<String> getContentAsync(){
       return executor.submit(() -> {           
           HttpRequest httpReq = HttpRequest.newBuilder()
               .uri(new URI("www.graalvm.org"))
               .build();
           return HttpClient.newHttpClient()
               .send(httpReq, BodyHandlers.ofString())
               .body();
       });
   }

}

var webPageResult = SomeAsyncCode.getContentAsync().get(); </syntaxhighlight>

Numerical capabilities

Several language constructs are designed to facilitate mathematical and financial computations.

Java's strictfp modifier guarantees consistent floating-point behavior across different systems by restricting intermediate calculations to single or double precision. Without this keyword, some platforms might utilize extended precision for intermediate steps. In contrast, C# permits hardware to apply higher precision in intermediate floating-point operations but does not provide a mechanism to enforce lower precision explicitly.

Although Java's floating-point operations largely comply with IEEE 754 standards, certain aspects such as Exception Flags and Directed Rounding remain unsupported even when strictfp is applied.

C# incorporates a native decimal type, a 128-bit numeric format offering greater precision but a narrower range compared to double, making it well-suited for financial applications. This type handles values approximately between 1.0 × 10−28 and 7.9 × 1028, with 28–29 digits of precision, and supports operator overloading for arithmetic expressions.

Java includes the and classes for arbitrary-precision decimal and integer arithmetic, respectively; however, it does not provide built-in complex number support.

In contrast, C# supplies and types that support arbitrary-precision integers and complex numbers, complete with operator overloading for arithmetic operations. Nonetheless, C# lacks built-in arbitrary-precision floating-point types.

Moreover, C# offers the and operators to toggle runtime checks for arithmetic overflow within designated code segments.

Language Integrated Query (LINQ)

A distinctive feature of C# is its Language Integrated Query (LINQ), which integrates query capabilities directly into the language, setting it apart from Java.

Key components of LINQ include:

  • Extension methods that allow adding new functionality to existing types without modifying them.
  • Lambda expressions enabling concise functional criteria definitions.
  • Expression trees that represent lambdas as abstract syntax trees, facilitating translation into other query languages such as SQL (e.g., LINQ to SQL).
  • Anonymous types combined with type inference to manage unnamed result types from complex queries.
  • Query expressions providing a syntax reminiscent of SQL.
  • Nullable (lifted) types enhancing compatibility with query providers that handle nullable data types like SQL databases.

Native interoperability

Native interoperability Java C#
Cross-language interoperability yes (through GraalVM, Nashorn, CORBA, JNI, JNA) yes; designed with this capability
External/native methods yes yes
Marshalling no; requires external glue code yes; controlled via metadata
Pointers and pointer arithmetic no; only accessible via sun.misc.Unsafe yes
Native data types yes yes
Fixed-size buffers no yes
Explicit stack allocation no yes
Address-of operator no yes
Object pinning no yes
Function pointers no yes
Unions no yes

Java's Java Native Interface (JNI) facilitates invoking native code but demands strict adherence to conventions and often necessitates additional bridging code written in C or C++. Java Native Access (JNA) simplifies native interactions by allowing calls solely from Java but at some performance cost. Third-party solutions exist to bridge Java with Component Object Model (COM), including free tools like JACOB and commercial products such as J-Integra.

On the other hand, .NET's Platform Invoke (P/Invoke) enables C# programs to call unmanaged APIs with fine-grained control over parameter marshalling through metadata attributes, removing the need for external glue code typical in JNI usage. P/Invoke supports extensive access to procedural APIs like Win32 and POSIX, although direct interaction with C++ class libraries remains limited. Additionally, the .NET Framework provides seamless integration with COM components via a dedicated bridge that treats COM objects as native .NET entities.

C# allows disabling certain CLR safety restrictions within unsafe code blocks, permitting pointer usage and low-level memory operations without exiting the managed environment. While unsafe code in C#, JNI, and P/Invoke all carry potential security and stability risks, unsafe code offers closer integration within managed workflows. Assemblies containing unsafe constructs must be compiled with special flags and are flagged for runtime security enforcement.

Runtime environments

Java applications operate on the Java platform through the Java Runtime Environment (JRE), which consists of the Java virtual machine (JVM) alongside standard libraries. The JRE primarily facilitates interpreted execution but also supports optional just-in-time compilation. Most implementations of the JRE execute programs that are either fully or partially compiled, frequently employing adaptive optimization techniques. The Java compiler generates Java bytecode, which is then loaded by the runtime environment and either interpreted or translated into machine-level instructions.

Conversely, C# executes within the Common Language Runtime (CLR), a framework optimized for fully compiled code. The C# compiler outputs Common Intermediate Language (CIL) instructions, which the CLR compiles into native machine code when the program runs.

Input/output

The following example demonstrates how to transfer text from one file to another line-by-line using both languages.

Java C#
<syntaxhighlight lang="Java" style="font-size:90%">

import java.nio.file.*;

class FileIOTest {

   public static void main(String[] args) throws Exception {
       var lines = Files.readAllLines(Paths.get("input.txt"));
       Files.write(Paths.get("output.txt"), lines);
   }

} </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%">

using System.IO;

class FileIOTest {

   public static void Main(string[] args) {
       var lines = File.ReadLines("input.txt");
       File.WriteAllLines("output.txt", lines);
   }

} </syntaxhighlight>

Details on Java:
  • The method <syntaxhighlight lang="text" class="" style="" inline="1">Files.readAllLines</syntaxhighlight> returns a List containing all lines from the file; alternatively, <syntaxhighlight lang="text" class="" style="" inline="1">readAllBytes</syntaxhighlight> yields a byte array.
  • <syntaxhighlight lang="text" class="" style="" inline="1">Files.write</syntaxhighlight> writes either byte arrays or strings to an output file designated by a Path.
  • Buffer management and stream closure are handled automatically by the API.
Details on C#:
  • ReadLines produces an enumerable sequence that reads lines lazily as needed.
  • WriteAllLines writes all lines from an enumerable and ensures the stream is closed afterwards.
  • Internal buffering is managed by the framework without requiring explicit buffer handling.

Integration of library-defined types

C# allows direct interaction between library-defined types and native operators through implicit or explicit conversions combined with operator overloading, as illustrated below:

Java C#

<syntaxhighlight lang=Java style="font-size:90%"> var bigNumber =

   new BigInteger("123456789012345678901234567890");

var answer = bigNumber.multiply(BigInteger.valueOf(42)); var square = bigNumber.sqrt(); var sum = bigNumber.add(bigNumber); </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%"> var bigNumber =

   BigInteger.Parse("123456789012345678901234567890");

var answer = bigNumber * 42; var square = bigNumber * bigNumber; var sum = bigNumber + bigNumber; </syntaxhighlight>

C# delegates and their Java counterparts

Java C#

<syntaxhighlight lang=Java style="font-size:90%"> // target class definition class Target {

   public boolean targetMethod(String arg) {
       // perform some operation
       return true;
   }

}

// usage example void doSomething() {

   var target = new Target();
   Function<String, Boolean> ivk = target::targetMethod;
   var result = ivk.apply("argumentstring");

} </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%">

class Target {

   public bool TargetMethod(string arg)
   {
       // perform some operation
       return true;
   }

}

void DoSomething() {

   var target = new Target();
   Func<string, bool> dlg = target.TargetMethod;
   bool result = dlg("argumentstring");

} </syntaxhighlight>

Type lifting

Java C#

Java does not offer built-in type lifting but emulates the concept using Optional: <syntaxhighlight lang="java"> var a = Optional.of(42); var b = Optional.<Integer>empty(); var c = a.flatMap(aa -> b.map(bb -> aa * bb)); </syntaxhighlight>

<syntaxhighlight lang="csharp">

int? a = 42; int? b = null; int? c = a * b; // result is null because of lifted operator with null operand </syntaxhighlight>

Interoperability with dynamic languages

This segment illustrates how Java and C# interact with dynamic languages such as Ruby, employing a Ruby-based "Deepthought" calculator class that multiplies two values ( and ).

Java C#

Using GraalVM

<syntaxhighlight lang=Java style="font-size:90%"> Context polyglot = Context.newBuilder().allowAllAccess(true).build();

Value rubyArray = polyglot.eval("ruby", "[1,2,42,4]"); int rubyResult = rubyArray.getArrayElement(2).asInt();

Value pythonArray = polyglot.eval("python", "[1,2,42,4]"); int pythonResult = pythonArray.getArrayElement(2).asInt();

Value jsArray = polyglot.eval("js", "[1,2,42,4]"); int jsResult = jsArray.getArrayElement(2).asInt();

Value rArray = polyglot.eval("R", "c(1,2,42,4)"); int rResult = rArray.getArrayElement(2).asInt();

Source source = Source.newBuilder("llvm", new File("C_Program.bc")).build(); Value cpart = polyglot.eval(source); cpart.getMember("main").execute(); </syntaxhighlight>

Traditional approach

<syntaxhighlight lang=java style="font-size:90%"> var invocable = new ScriptEngineManager().getEngineByName("jruby"); var rubyFile = new FileReader("Deepthought.rb"); engine.eval(rubyFile);

var calcClass = engine.eval("Deepthought"); var calc = invocable.invokeMethod(calcClass, "new");

invocable.invokeMethod(calc, "a=", 6); invocable.invokeMethod(calc, "b=", 7);

var answer = invocable.invokeMethod(calc, "Calculate"); </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%">

var runtime = ScriptRuntime.CreateFromConfiguration(); dynamic globals = runtime.Globals;

runtime.ExecuteFile("Deepthought.rb");

var calc = globals.Deepthought.@new(); calc.a = 6; calc.b = 7;

var answer = calc.Calculate(); </syntaxhighlight>

Java-specific notes:

  • When setting Ruby properties, the <syntaxhighlight lang="text" class="" style="" inline="1">=</syntaxhighlight> suffix is necessary for accessor methods.
  • Dynamic foreign objects are accessed through an API rather than being native language constructs.

C#-specific notes:

  • Members and methods returning dynamic types remain dynamic.
  • Variables declared with var are bound at runtime.
  • Dynamic objects integrate naturally with C# language syntax.
  • The <syntaxhighlight lang="text" class="" style="" inline="1">@</syntaxhighlight> symbol allows usage of reserved keywords like new as identifiers.

Fibonacci sequence

This example presents implementations of the Fibonacci sequence in both Java and C#. The C# variant utilizes generator methods, while the Java version employs the interface along with method references. Both follow the K&R style formatting convention.

Java C#
<syntaxhighlight lang=Java style="font-size:90%">

// Fibonacci sequence Stream.generate(new Supplier<Integer>() {

   int a = 0;
   int b = 1;
   public Integer get() {
       int temp = a;
       a = b;
       b = a + temp;
       return temp;
   }

}).limit(10).forEach(System.out::println); </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%">

// Fibonacci sequence public IEnumerable<int> Fibonacci() {

   int a = 0;
   int b = 1;
   while (true) 
   {
       yield return a;
       (a, b) = (b, a + b);
   }

} </syntaxhighlight>

<syntaxhighlight lang="csharp" style="font-size:90%">

// output first 10 Fibonacci numbers foreach (var it in Fibonacci().Take(10)) {

   Console.WriteLine(it);

} </syntaxhighlight>

Java notes:
  • The Stream interface facilitates both sequential and parallel aggregate operations.
  • generate produces an endless stream where each element is provided by the given Supplier.
  • limit restricts the stream to a maximum number of elements.
  • forEach applies an action to each item, such as invoking a method reference.

Using foreach

An alternative Java method returns an Iterable to better resemble C#'s foreach usage. Any Iterable can be iterated over in a foreach loop. <syntaxhighlight lang="java" style="font-size:90%"> Iterable<Integer> fibonacci(int limit) {

   return Stream.generate(new Supplier<Integer>() {
       int a = 0;
       int b = 1;
       
       public Integer get() {
           int temp = a;
           a = b;
           b = a + temp;
           return temp;
       }
   }).limit(limit)::iterator;

} </syntaxhighlight>

<syntaxhighlight lang="java" style="font-size:90%"> // output first 10 Fibonacci numbers for(int it: fibonacci(10)) {

   System.out.println(it);

} </syntaxhighlight>

The conventional Java approach often uses Streams directly and collects results with toList inside the foreach: <syntaxhighlight lang="java" style="font-size:90%"> var fibonacci = Stream.generate(new Supplier<Integer>() {

   int a = 0;
   int b = 1;
           
   public Integer get() {
       int temp = a;
       a = b;
       b = a + temp;
       return temp;
   }

}); </syntaxhighlight>

<syntaxhighlight lang="java" style="font-size:90%"> // output first 10 Fibonacci numbers for(int it: fibonacci.limit(10).toList()) {

   System.out.println(it);

} </syntaxhighlight>

Besides toList, multiple collectors exist for different data structures. Custom collectors can be implemented by creating classes that implement the Collector interface or by passing lambda expressions to Stream's collect method. This is particularly beneficial when collecting complex objects.

Both examples could be adapted to use IntStream and IntSupplier to avoid boxing of primitive integers but generics were preserved here for closer comparison with C#.

Functional Style

The algorithm can be further streamlined using <syntaxhighlight lang="text" class="" style="" inline="1">Stream.iterate</syntaxhighlight>, which requires an initial seed value and an iteration function. Employing a record class to hold two values removes the necessity of implementing Supplier: <syntaxhighlight lang="java" style="font-size:90%">

record Pair(int a, int b) {};

Stream

 .iterate(new Pair(0, 1), p -> new Pair(p.b, p.a + p.b))
 .limit(10)
 .map(p -> p.a)
 .forEach(System.out::println);

</syntaxhighlight>

C# notes:

  • Returns an Template:Csharp enabling repeated retrieval of subsequent sequence values.
  • The Template:Csharp keyword defines generator methods.
  • Template:Csharp yields the next element while preserving local state across calls.
  • Tuple assignment updates variables efficiently without temporary storage overhead.

See also

External links

  1. ↑ Lua error in Module:Citation/CS1/Configuration at line 2123: attempt to index field '?' (a nil value).