Difference between revisions of "Programming in D for CSharp Programmers"

From D Wiki
Jump to: navigation, search
(Interfaces: syntaxhighlight)
m (switch Statement: minor reformat.)
(37 intermediate revisions by 3 users not shown)
Line 6: Line 6:
 
<syntaxhighlight lang="C#">
 
<syntaxhighlight lang="C#">
 
using System;
 
using System;
class Hello {
+
class Hello
  static void Main(string[] args) {
+
{
      Console.WriteLine("Hello world!");
+
    static void Main(string[] args)
  }
+
    {
 +
        Console.WriteLine("Hello world!");
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 18: Line 20:
 
<syntaxhighlight lang="D">
 
<syntaxhighlight lang="D">
 
import std.stdio;
 
import std.stdio;
void main(string[] args) {
+
void main(string[] args)
  writeln("Hello world!");
+
{
 +
    writeln("Hello world!");
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 34: Line 37:
 
= Documentation Comments =
 
= Documentation Comments =
 
''The C# Way''
 
''The C# Way''
 +
<syntaxhighlight lang="C#">
 
  /// <summary>
 
  /// <summary>
 
  /// Documentation comment
 
  /// Documentation comment
Line 40: Line 44:
 
  /// <param name="bar">...</param>
 
  /// <param name="bar">...</param>
 
  /// <returns>...</returns>
 
  /// <returns>...</returns>
 +
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
 +
<syntaxhighlight lang="D">
 
  /**
 
  /**
* Documentation comment
+
  Documentation comment
  *
+
   
* Params:  foo = param description
+
  Params:  foo = param description
*          bar = param description
+
          bar = param description
  *
+
   
* Returns: return value
+
  Returns: return value
  *
+
   
* Throws:  Exception  
+
  Throws:  Exception  
 
  */
 
  */
 +
</syntaxhighlight>
  
 
= Compilation =  
 
= Compilation =  
Line 72: Line 79:
  
 
  -define:S1[;S2]
 
  -define:S1[;S2]
 
+
<syntaxhighlight lang="C#">
// compile with: /define:xx
+
// compile with: /define:xx
// or uncomment the next line:
+
// or uncomment the next line:
//#define xx
+
//#define xx
 
   
 
   
[Test] public void define_test() {
+
[Test] public void define_test() {
 
     int x = 0;
 
     int x = 0;
 
     #if (xx)
 
     #if (xx)
Line 85: Line 92:
 
     #endif
 
     #endif
 
     Assert.AreEqual(x, 5);
 
     Assert.AreEqual(x, 5);
}
+
}
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
  
 
  -version=S1
 
  -version=S1
 
+
<syntaxhighlight lang="D">
// compile with: -version=xx
+
// compile with: -version=xx
// or uncomment the next line:
+
// or uncomment the next line:
//version = xx
+
//version = xx
 
   
 
   
unittest {
+
unittest {
 
     int x = 0;
 
     int x = 0;
 
     version(xx) {
 
     version(xx) {
Line 104: Line 111:
 
     }
 
     }
 
     assert( x == 5 );
 
     assert( x == 5 );
}
+
}
 +
</syntaxhighlight>
  
 
= Coding styles =
 
= Coding styles =
Line 206: Line 214:
 
''The C# Way''
 
''The C# Way''
  
List<T> works very similarly to a dynamic array
+
List<T> behaves similarly to a dynamic array
using System.Collections.Generic;
+
<syntaxhighlight lang="C#">
...
+
using System.Collections.Generic;
List<string> fruits = new List<string>{"banana", "mango"};
+
...
Assert.AreEqual(fruits.Count, 2);
+
List<string> fruits = new List<string>{"banana", "mango"};
 +
Assert.AreEqual(fruits.Count, 2);
 
   
 
   
fruits.Add("strawberry");
+
fruits.Add("strawberry");
Assert.AreEqual(fruits.Count, 3);
+
Assert.AreEqual(fruits.Count, 3);
Assert.AreEqual(fruits[2], "strawberry");
+
Assert.AreEqual(fruits[2], "strawberry");
 
+
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
string[] fruits = ["banana", "mango"];
+
<syntaxhighlight lang="D">
assertEquals(fruits.length, 2);
+
string[] fruits = ["banana", "mango"];
 +
assertEquals(fruits.length, 2);
 
   
 
   
fruits ~= "strawberry";
+
fruits ~= "strawberry";
assertEquals(fruits.length, 3);
+
assertEquals(fruits.length, 3);
assertEquals(fruits[2], "strawberry");
+
assertEquals(fruits[2], "strawberry");
 +
</syntaxhighlight>
  
 
== Pointers ==
 
== Pointers ==
Line 229: Line 240:
  
 
''The C# Way''
 
''The C# Way''
int value;
+
<syntaxhighlight lang="C#">
// here you can't use pointers
+
int value;
unsafe {
+
// here you can't use pointers
   int* p = &amp;value
+
unsafe {
}
+
   int* p = &value;
 
+
}
 +
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
int value;
+
int value;
int* p = &amp;value
+
int* p = &value;
@safe {
+
@safe {
 
   //here you can't use pointers
 
   //here you can't use pointers
}
+
}
 +
</syntaxhighlight>
  
 
== Delegates ==
 
== Delegates ==
Line 248: Line 261:
  
 
''The C# Way''
 
''The C# Way''
delegate int F
+
<syntaxhighlight lang="C#">
 
+
delegate int F
 +
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
int delegate(int x) Foo;
+
<syntaxhighlight lang="D">
int function(int x) Foo;
+
int delegate(int x) Foo;
 
+
int function(int x) Foo;
 +
</syntaxhighlight>
 
Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that '''delegates are not multicast in D''', therefore you cannot join or remove them.
 
Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that '''delegates are not multicast in D''', therefore you cannot join or remove them.
  
Line 261: Line 276:
  
 
''The C# Way''
 
''The C# Way''
enum Option {  
+
<syntaxhighlight lang="C#">
 +
enum Option  
 +
{  
 
     Option1,  
 
     Option1,  
 
     Option2
 
     Option2
}
+
}
 
   
 
   
[Flags]
+
[Flags]
enum Permission {
+
enum Permission
 +
{
 
     read,
 
     read,
 
     write,
 
     write,
 
     all = read | wite
 
     all = read | wite
}
+
}
 +
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
  
 
The members of enums should be camelCased, so their first letter is lowercase. (see [http://dlang.org/dstyle.html D Style]
 
The members of enums should be camelCased, so their first letter is lowercase. (see [http://dlang.org/dstyle.html D Style]
enum Color {   
+
 
 +
<syntaxhighlight lang="C#">
 +
enum Color
 +
{   
 
   red,  
 
   red,  
 
   green,  
 
   green,  
 
   blue
 
   blue
}
+
}
 
   
 
   
enum Permission {
+
enum Permission
 +
{
 
     read,
 
     read,
 
     write,
 
     write,
 
     all = read | wite
 
     all = read | wite
}
+
}
 +
</syntaxhighlight>
  
 
== Struct ==
 
== Struct ==
Line 295: Line 319:
  
 
''The C# Way''
 
''The C# Way''
public struct TimeOfDay {
+
<syntaxhighlight lang="C#">
 +
public struct TimeOfDay {
 
   public int hour;
 
   public int hour;
 
   public int minute;
 
   public int minute;
Line 302: Line 327:
 
     minute = m;
 
     minute = m;
 
   }
 
   }
}
+
}
...
+
...
 +
 
 +
var t1 = new TimeOfDay(8, 30);
 +
Assert.AreEqual(t1.minute, 30);
 
   
 
   
var t1 = new TimeOfDay(8, 30);
+
// Declare a struct object without "new"
Assert.AreEqual(t1.minute, 30);
+
TimeOfDay t2;
+
t2.minute = 10;
// Declare a struct object without "new"
+
Assert.AreEqual(t2.minute, 10);
TimeOfDay t2;
+
</syntaxhighlight>
t2.minute = 10;
 
Assert.AreEqual(t2.minute, 10);
 
 
 
 
''The D Way''
 
''The D Way''
struct TimeOfDay {
+
<syntaxhighlight lang="D">
 +
struct TimeOfDay {
 
   int hour;
 
   int hour;
 
   int minute;
 
   int minute;
}
+
}
...  
+
...  
 
   
 
   
// preferred syntax
+
// preferred syntax
auto t1 = TimeOfDay(8, 30);   
+
auto t1 = TimeOfDay(8, 30);   
assert(t1.hour == 8);
+
assert(t1.hour == 8);
assert(t1.minute == 30);
+
assert(t1.minute == 30);
 
   
 
   
// alternate C syntax, deprecated
+
// alternate C syntax, deprecated
TimeOfDay t2 = {9, 45};       
+
TimeOfDay t2 = {9, 45};       
assert(t2.hour == 9);
+
assert(t2.hour == 9);
 
   
 
   
// with designated initializers
+
// with designated initializers
auto t3 = {minute: 20, hour: 8};
+
auto t3 = {minute: 20, hour: 8};
assert(t3.hour == 8);
+
assert(t3.hour == 8);
assert(t3.minute == 20);
+
assert(t3.minute == 20);
 
    
 
    
auto t4 = TimeOfDay(10);    // not all members need to be specified
+
auto t4 = TimeOfDay(10);    // not all members need to be specified
assert(t4.hour == 10);
+
assert(t4.hour == 10);
assert(t4.minute == 0);
+
assert(t4.minute == 0);
 +
</syntaxhighlight>
  
 
== Union ==
 
== Union ==
Line 342: Line 369:
  
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
[StructLayout(LayoutKind.Explicit)]
+
[StructLayout(LayoutKind.Explicit)]
struct MyUnion {
+
struct MyUnion {
 
   [FieldOffset(0)]
 
   [FieldOffset(0)]
 
   int someInt;
 
   int someInt;
 
   [FieldOffset(0)]
 
   [FieldOffset(0)]
 
   float someFloat;
 
   float someFloat;
}
+
}
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
union MyUnion {
+
union MyUnion {
 
   int someInt;
 
   int someInt;
 
   float someFloat;
 
   float someFloat;
}
+
}
 
+
</syntaxhighlight>
  
 
== Interfaces ==
 
== Interfaces ==
Line 365: Line 392:
 
''The C# Way''
 
''The C# Way''
 
<syntaxhighlight lang="C#">
 
<syntaxhighlight lang="C#">
interface I {
+
interface I
  void Method();
+
{
 +
    void Method();
 
}
 
}
 
   
 
   
abstract class C {
+
abstract class C
  void Method() {
+
{
    Console.WriteLine("hello");
+
    void Method()
  }
+
    {
 +
      Console.WriteLine("hello");
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
<syntaxhighlight lang="D">
 
<syntaxhighlight lang="D">
interface I {
+
interface I
  void method();
+
{
 +
    void method();
 
}
 
}
 
   
 
   
interface C {
+
interface C
  final void method() {
+
{
      writeln("hello");
+
    final void method()
  }
+
    {
 +
        writeln("hello");
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 390: Line 423:
 
== Classes ==
 
== Classes ==
 
''The C# Way''
 
''The C# Way''
class A {
+
<syntaxhighlight lang="C#">
 +
class A {
 
     private int myValue;  // not accessible from outside
 
     private int myValue;  // not accessible from outside
 
                                                                                      
 
                                                                                      
Line 400: Line 434:
 
     }
 
     }
 
                                                                                      
 
                                                                                      
}
+
}
 
                                                                                      
 
                                                                                      
class B: A {
+
class B: A {
 
     public B(): base(3) {}
 
     public B(): base(3) {}
 
                                                                                      
 
                                                                                      
Line 409: Line 443:
 
       return this.Value * 2;
 
       return this.Value * 2;
 
     }
 
     }
}
+
}
 
                                                                                      
 
                                                                                      
[TestFixture] public class AboutClasses {
+
[TestFixture] public class AboutClasses {
 
     [Test]public void inheritance() {
 
     [Test]public void inheritance() {
 
       B instance = new B();
 
       B instance = new B();
 
       Assert.AreEqual(instance.GetDoubleValue(), 6);
 
       Assert.AreEqual(instance.GetDoubleValue(), 6);
 
     }
 
     }
}
+
}
 
+
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
class A {
+
<syntaxhighlight lang="D">
 +
class A {
 
   private int myValue;  // not accessible from outside
 
   private int myValue;  // not accessible from outside
 
   
 
   
Line 426: Line 461:
 
     myValue = startValue;
 
     myValue = startValue;
 
   }
 
   }
}
+
}
 
   
 
   
class B: A {
+
class B: A {
 
   this() {
 
   this() {
     super(3);  // what happens here ?
+
     super(3);  // call constructor on parent class A
 
   }  
 
   }  
 
   
 
   
 
   auto getDoubleValue() {
 
   auto getDoubleValue() {
     return myValue * 2;  // B doesn't have this field but..
+
     return myValue * 2;  // B doesn't have this field but can access the value
 
   }
 
   }
}
+
}
 
   
 
   
class AboutClasses {
+
class AboutClasses {
 
    
 
    
 
   @Test public void inheritance() {
 
   @Test public void inheritance() {
Line 444: Line 479:
 
       assertEquals(instance.getDoubleValue(), 6);
 
       assertEquals(instance.getDoubleValue(), 6);
 
     }
 
     }
}
+
}
 +
</syntaxhighlight>
  
 
== Generic Types ==
 
== Generic Types ==
Line 451: Line 487:
  
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
public class A {
+
public class A {
 
   public void Foo<T>(T arg) {}
 
   public void Foo<T>(T arg) {}
}
+
}
// use
+
// use
A a = new A();
+
A a = new A();
a.Foo<int>(1);
+
a.Foo<int>(1);
 
   
 
   
 
   
 
   
public class C<T> where T: class { }
+
public class C<T> where T: class { }
...                                           
+
...                                           
C<A> c = new C<A>();
+
C<A> c = new C<A>();
// C<int> i = new C<int>(); compiler error: int isn't a class
+
// C<int> i = new C<int>(); compiler error: int isn't a class
 
   
 
   
 
   
 
   
public class X { }
+
public class X { }
public class D<T> where T: X {}
+
public class D<T> where T: X {}
 
   
 
   
  D<X> d = new D<X>();
+
D<X> d = new D<X>();
//D<A> d = new D<A>();compiler error: A isn't X
+
//D<A> d = new D<A>();compiler error: A isn't X
 
+
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
void foo(T)(T arg) {}
+
void foo(T)(T arg) {}
// use
+
// use
foo!int(3);
+
foo!int(3);
foo!string("bar");
+
foo!string("bar");
 
   
 
   
class X {}  
+
class X {}  
class C(T) if is(T == class) {}
+
class C(T) if is(T == class) {}
 
   
 
   
// use
+
// use
C c = new C!X;
+
C c = new C!X;
// C c = new C!int;  compiler error: int isn't a class
+
// C c = new C!int;  compiler error: int isn't a class
 
   
 
   
 
   
 
   
class D(T) if is(T : X) {}
+
class D(T) if is(T : X) {}
auto d = new D!X;
+
auto d = new D!X;
//auto d = new D!(A);compiler error: A isn't X
+
//auto d = new D!(A);compiler error: A isn't X
 
+
</syntaxhighlight>
 
=== Constraints ===
 
=== Constraints ===
 
There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own.
 
There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own.
  
 
''The C# Way''
 
''The C# Way''
 +
<syntaxhighlight lang="C#">
 +
class C<T> where T: new() {}
 +
</syntaxhighlight>
 +
''The D Way''
 +
<syntaxhighlight lang="D">
 +
class C(T) if (is(typeof(new T()) == T))
 +
</syntaxhighlight>
 +
The template constraint will be read as if the result of expression <code>new T()</code> is of type T. If the T class has no constructor or is some other type, the <code>new T()</code> expression will result in an error or some other type, therefore the constraint will not be satisfied.
 +
 +
== Events ==
 +
 +
There is no such concept in D language, but D programmers prefer to use [http://dlang.org/phobos/std_signals.html signals and slots] instead of events.
 +
 +
If you are keen to use events in D, a quick and dirty way to implement them can be found below (Credit: https://forum.dlang.org/post/dcdtuqyrxpteuaxmvwft@forum.dlang.org):
 +
 +
<syntaxhighlight lang="D">
 +
class Event(T...)
 +
{
 +
    alias CB = void delegate(T);
 +
    CB[] callbacks;
 +
 +
    // NOTE: This implementation uses the `+` operator as it is likely more
 +
    // familiar to C# programmers, but a more idiomatic D operator would be
 +
    // `~`, D's concatenation operator.
 +
 +
    void opOpAssign(string op)(CB handler)
 +
        if (op == "+" || op == "-")
 +
    {
 +
        static if (op == "+")
 +
            callbacks ~= handler;
 +
        else
 +
        {
 +
            import std.algorithm.mutation : remove;
 +
            callbacks = callbacks.remove!(x => x == handler);
 +
        }
 +
    }
 +
 +
    void opOpAssign(string op)(void function(T) handler)
 +
        if (op == "+" || op == "-")
 +
    {
 +
        import std.functional : toDelegate;
 +
        opOpAssign!op(toDelegate(handler));
 +
    }
 +
 +
    void opCall(T args)
 +
    {
 +
        foreach (cb; callbacks)
 +
            cb(args);
 +
    }
 +
}
 +
 +
class Property(T)
 +
{
 +
    private T _value;
  
class C<T> where T: new() {}
+
    alias event = Event!(T, T);
  
''The D Way''
+
    auto changing = new event();
 +
    auto changed = new event();
  
class C(T) if (is(typeof(new T()) == T))
+
    T value() @property
 +
    {
 +
        return _value;
 +
    }
  
The template constraint will be read as if the result of expression <code>new T()</code> is of type T. If the T class has no contructor or is some other type, the <code>new T()</code> expression will result in an error or some other type, therefore the constraint will not be satisfied.
+
    T value(T newValue) @property
 +
    {
 +
        if (_value != newValue)
 +
        {
 +
            T oldValue = _value;
 +
            changing(_value, newValue);
 +
            _value = newValue;
 +
            changed(oldValue, _value);
 +
        }
 +
        return _value;
 +
    }
 +
}
  
== Events ==
+
void main()
 +
{
 +
    auto test = new Property!(int)();
  
There is no such concept in D language, but D programmers prefer to use [http://dlang.org/phobos/std_signals.html signals and slots] instead of events.  
+
    int changingCount;
 +
    void onChanging(int oldValue, int newValue)
 +
    {
 +
        changingCount++;
 +
    }
 +
    test.changing += &onChanging;
  
If you are keen to use events in D, a quick and dirty way to implement them can be found below:
+
    int changedCount;
 +
    void onChanged(int oldValue, int newValue)
 +
    {
 +
        changedCount++;
 +
    }
 +
    test.changed += &onChanged;
 +
   
 +
    test.value = 1;  // value changed from 0 to 1
 +
    test.value = 1;  // value not changed
 +
    test.value = 2;  // value changed from 1 to 2
  
'''TO DO'''
+
    assert(changingCount == 2);
 +
    assert(changedCount == 2);
 +
}
 +
</syntaxhighlight>
  
 
== Dynamic Types ==
 
== Dynamic Types ==
Line 528: Line 652:
  
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="D">
[Test]public void boxing_test() {
+
[Test]public void boxing_test() {
 
     int a = 1;
 
     int a = 1;
 
     float b = 19.64f;
 
     float b = 19.64f;
Line 543: Line 667:
 
     Assert.AreEqual(b, 19.64f);
 
     Assert.AreEqual(b, 19.64f);
 
     Assert.AreEqual(tos(o), "19.64");
 
     Assert.AreEqual(tos(o), "19.64");
}
+
}
private string tos(object obj) {
+
private string tos(object obj) {
 
     return obj.ToString();
 
     return obj.ToString();
}
+
}
 +
</syntaxhighlight>
  
 +
''The D Way''
 +
<syntaxhighlight lang="D">
 +
import std.variant;
  
''The D Way''
+
@Test void boxing_test() {
@Test void boxing_test() {
 
 
     int a = 1;  
 
     int a = 1;  
 
     float b = 19.64f;
 
     float b = 19.64f;
Line 561: Line 688:
 
     auto x = v.get!(float);
 
     auto x = v.get!(float);
 
     assert(x == 19.64f);
 
     assert(x == 19.64f);
}
+
}
 
                                    
 
                                    
string tos(T)(T obj) {
+
string tos(T)(T obj) {
 
     return to!string(obj);
 
     return to!string(obj);
}
+
}
 +
</syntaxhighlight>
  
 
== Nullable types ==
 
== Nullable types ==
 
Value types are not nullable in the D language.  
 
Value types are not nullable in the D language.  
But there are solutions in the standard library module typecons to simulate this behavior.
+
But there is a solution in the standard library module [[typecons]] - templated struct [[Nullable]] - to simulate this behavior. This is the same solution as in C#, although C# provides the shorthand syntax of "Type? variable-name", which can hide the usage of templated struct System.Nullable<>.
  
 
''[https://msdn.microsoft.com/en-us/library/vstudio/1t3y8s4s%28v=vs.110%29.aspx The C# Way]''
 
''[https://msdn.microsoft.com/en-us/library/vstudio/1t3y8s4s%28v=vs.110%29.aspx The C# Way]''
 
<syntaxhighlight lang="C#">
 
<syntaxhighlight lang="C#">
int? num = null;
+
int? a = null; // short hand for: System.Nullable<int> a = null;
 +
// attempting to use a.Value will cause an exception to be thrown
 +
int? num = 10;
 
if (num.HasValue) {
 
if (num.HasValue) {
     System.Console.WriteLine("num = " + num.Value);
+
     System.Console.WriteLine(3 * num.Value); // prints 30
 
} else {
 
} else {
 
     System.Console.WriteLine("num = Null");
 
     System.Console.WriteLine("num = Null");
Line 586: Line 716:
 
import std.typecons;
 
import std.typecons;
  
 +
Nullable!int a; // a is null and cannot be used as an int until assigned a value
 +
                // attempting to use a as an int will cause an exception to be thrown
 
Nullable!int num = 10;
 
Nullable!int num = 10;
if (num.isNull) {
+
if ( ! num.isNull) {
     writeln(num.get());
+
     writeln(num.get * 3);  // prints 30
 +
    writeln(num * 3);      // prints 30 - get not required (see below)
 +
} else {
 +
    writeln("num = Null");
 
}
 
}
int y = num.get();
+
int y = num;  // get property not required due to "alias get this" inside Nullable for
int z = num.isNull ? int.init : num.get();
+
              // automatic compiler conversion to int when Nullable!(int) is not appropriate
num.nullify();
+
int z = num.isNull ? int.init : num;
 +
num.nullify();   // num set to null - cannot be used as an int until re-assigned
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 603: Line 739:
  
 
''The C# Way''
 
''The C# Way''
class Prime: IEnumerable {
+
<syntaxhighlight lang="C#">
 +
class Prime: IEnumerable {
 
     object[] items = {1, 3, 5, 7};
 
     object[] items = {1, 3, 5, 7};
 
                                                                            
 
                                                                            
Line 613: Line 750:
 
       }
 
       }
 
     }
 
     }
}
+
}
 
   
 
   
[TestFixture] public class TestIterator {
+
[TestFixture] public class TestIterator {
 
     [Test] public void prime_test() {
 
     [Test] public void prime_test() {
 
       Prime primes = new Prime();
 
       Prime primes = new Prime();
Line 625: Line 762:
 
       }
 
       }
 
     }
 
     }
}
+
}
 
+
</syntaxhighlight>
 
''The D Way (front, popFront, empty)''
 
''The D Way (front, popFront, empty)''
 
+
<syntaxhighlight lang="D">
class Prime {
+
class Prime {
 
     private int[] p = [1, 3, 5, 7];
 
     private int[] p = [1, 3, 5, 7];
 
     @property bool empty() {
 
     @property bool empty() {
Line 640: Line 777:
 
       p = p[1..$];
 
       p = p[1..$];
 
     }
 
     }
  }  
+
}  
  
  class TestIterator {
+
class TestIterator {
 
   mixin UnitTest;
 
   mixin UnitTest;
 
   
 
   
Line 653: Line 790:
 
       }
 
       }
 
   }
 
   }
}
+
}
 
+
</syntaxhighlight>
 
''The D Way (opApply)''
 
''The D Way (opApply)''
  
Line 663: Line 800:
 
''The C# Way''
 
''The C# Way''
  
class Person {
+
<syntaxhighlight lang="C#">
  public Person() {
+
class Person
    Name = "Default Name";
+
{
  }
+
    public Person()
   public string Name { get; set; }
+
    {
 +
        Name = "Default Name";
 +
    }
 +
    
 +
    public string Name { get; set; }
 
  }
 
  }
 +
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
class Person {
+
<syntaxhighlight lang="C#">
  private string _name;
+
class Person  
  this() {
+
{
    _name = "Default Name";
+
    private string _name;
  }
+
   
 +
    this()  
 +
    {
 +
        _name = "Default Name";
 +
    }
 
   
 
   
  @property string name() {
+
    @property string name()
    return _name;
+
    {
  }
+
        return _name;
  @property void name(string n) {
+
    }
    _name = n;
+
 
  }
+
    @property void name(string n)
}
+
    {
 +
        _name = n;
 +
    }
 +
}
 +
</syntaxhighlight>
  
 
= Statements =
 
= Statements =
Line 689: Line 839:
 
== Declaring variables ==
 
== Declaring variables ==
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
int x;
+
int x;
string text = "abc";
+
string text = "abc";
const double PI = 3.14;
+
const double PI = 3.14;
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
int x;
+
int x;
string text = "abc";
+
string text = "abc";
enum double PI = 3.14;
+
enum double PI = 3.14;
 
+
</syntaxhighlight>
  
 
== Expressions ==
 
== Expressions ==
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
double average = (a + b) / 2; // assignment expression
+
double average = (a + b) / 2; // assignment expression
foo.Bar(); // method call
+
foo.Bar(); // method call
 
   
 
   
SomeClass c = new SomeClass(); // class initialization
+
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = new SomeStruct(); //struct initialization
+
SomeStruct s = new SomeStruct(); //struct initialization
 
   
 
   
List<int> list = new List<int>();
+
List<int> list = new List<int>();
Dictionary<string, int> = new Dictionary<string, int>();
+
Dictionary<string, int> = new Dictionary<string, int>();
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
double average = (a + b) / 2;  // assignment expression
+
double average = (a + b) / 2;  // assignment expression
foo.bar(); // method call
+
foo.bar(); // method call
 
   
 
   
SomeClass c = new SomeClass(); // class initialization
+
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = SomeStruct(); //struct initialization
+
SomeStruct s = SomeStruct(); //struct initialization
 
   
 
   
List!int list = new List!int();
+
List!int list = new List!int();
Dictionary!(string, int) = new Dictionary!(string, int);
+
Dictionary!(string, int) = new Dictionary!(string, int);
 
+
</syntaxhighlight>
 
Differences:
 
Differences:
 
* Structs in D are initialized without the <code>new</code> keyword.
 
* Structs in D are initialized without the <code>new</code> keyword.
* Generic types in D are initialized using <code>!</code> and by specifing types between parantheses. If there is only one specialisation, parantheses can be omitted.
+
* Generic types in D are initialized using <code>!</code> and by specifing types between parentheses. If there is only one specialization, parentheses can be omitted.
  
 
== Using namespaces ==
 
== Using namespaces ==
Line 733: Line 883:
  
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
using System;
+
using System;
using System.Collections;
+
using System.Collections;
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
 
+
<syntaxhighlight lang="D">
import system;
+
import system;
//this will try to import 'system.d' from the current folder.  
+
//this will try to import 'system.d' from the current folder.  
//if 'system' is a folder name, it will try to import a special file named 'package.d'
+
//if 'system' is a folder name, it will try to import a special file named 'package.d'
import system.collections;
+
import system.collections;
//this will import the file 'collections.d' from a folder named 'system'.
+
//this will import the file 'collections.d' from a folder named 'system'.
 
+
</syntaxhighlight>
 
if you are creating your own namespaces, the closest match for simulating C# namespaces is a trick used to import C++ code in D:
 
if you are creating your own namespaces, the closest match for simulating C# namespaces is a trick used to import C++ code in D:
  
 
''The D Way''
 
''The D Way''
extern (C++, MyNamespace) {
+
<syntaxhighlight lang="D">
 +
extern (C++, MyNamespace) {
 
     class MyClass { ...}
 
     class MyClass { ...}
}
+
}
//Fully qualified name of MyClass is MyNamespace.MyClass  
+
//Fully qualified name of MyClass is MyNamespace.MyClass
 
+
</syntaxhighlight>
  
 
== if Statement ==
 
== if Statement ==
Line 758: Line 909:
  
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
bool b;  
+
bool b;  
if (b) { ... }
+
if (b) { ... }
 
   
 
   
int a;
+
int a;
if (a == 10) { .... } else { ... }
+
if (a == 10) { .... } else { ... }
 
   
 
   
SomeClass c;
+
SomeClass c;
if (c == null) { ... }
+
if (c == null) { ... }
void* p;
+
void* p;
if (p != IntPtr.Zero) { ... }
+
if (p != IntPtr.Zero) { ... }
 
   
 
   
string s = string.Empty;
+
string s = string.Empty;
if (string.IsNullOrEmpty(s)) { ... }
+
if (string.IsNullOrEmpty(s)) { ... }
 
+
</syntaxhighlight>
  
 
''The D Way''
 
''The D Way''
bool b;  
+
<syntaxhighlight lang="D">
if (b) { ... }
+
bool b;  
 +
if (b) { ... }
 
   
 
   
int a;
+
int a;
if (a == 10) { .... } else { ... }
+
if (a == 10) { .... } else { ... }
if (a) { ... }
+
if (a) { ... }
 
   
 
   
SomeClass c;
+
SomeClass c;
if (c is null) { ... }
+
if (c is null) { ... }
if (!c) { ... }
+
if (!c) { ... }
 
   
 
   
void* p;
+
void* p;
if (p) { ... }
+
if (p) { ... }
 
   
 
   
string s = "";
+
string s = "";
if(s.length == 0) { ... }
+
if(s.length == 0) { ... }
 
+
</syntaxhighlight>
  
 
Differences:
 
Differences:
Line 798: Line 950:
 
* In D, the conditional expression can be any other type than bool
 
* In D, the conditional expression can be any other type than bool
  
 +
== switch Statement ==
 +
The <code>switch</code>  case statement has exactly the same structure as in C#, except that D provides a final switch statement intended to use with enum types.
  
== switch Statement ==
 
The <code>switch</code>  case statement has exactly the same structure as in C#, except that D provides a final switch statement intended to use with enum types
 
 
''The C# Way''
 
''The C# Way''
 
+
<syntaxhighlight lang="C#">
enum Color { Blue, Red, Green }
+
enum Color { Blue, Red, Green }
 
   
 
   
Color c;
+
Color c;
switch (c) {
+
switch (c) {
 
     case Color.Blue: ... break;
 
     case Color.Blue: ... break;
 
     case Color.Red:  ... break;
 
     case Color.Red:  ... break;
 
     default: ... break;
 
     default: ... break;
}
+
}
 
+
</syntaxhighlight>
 
''The D Way''
 
''The D Way''
enum Color { Blue, Red, Green }
+
<syntaxhighlight lang="D">
 +
enum Color { Blue, Red, Green }
 
   
 
   
Color c;
+
Color c;
final switch (c) {
+
final switch (c) {
 
     case Color.Blue: ... break;
 
     case Color.Blue: ... break;
 
     case Color.Red:  ... break;
 
     case Color.Red:  ... break;
 
     //compilation error if the switch statement does not treat all members of Color.
 
     //compilation error if the switch statement does not treat all members of Color.
}
+
}
 
+
</syntaxhighlight>
 
or [http://p0nce.github.io/d-idioms/#Qualified-switch-using-with using with]  
 
or [http://p0nce.github.io/d-idioms/#Qualified-switch-using-with using with]  
  
 
''The D Way''
 
''The D Way''
  
// The members of enums should be camelCased
+
<syntaxhighlight lang="D">
enum Color { blue, red, green }
+
// The members of enums should be camelCased
 +
enum Color { blue, red, green }
 
   
 
   
Color c;
+
Color c;
 
   
 
   
final switch() with (Color){
+
final switch() with (Color){
 
     case blue: ... break;
 
     case blue: ... break;
 
     case red:  ... break;
 
     case red:  ... break;
 
     case green:  ... break;
 
     case green:  ... break;
 
     //compilation error if the switch statement does not treat all members of Color.
 
     //compilation error if the switch statement does not treat all members of Color.
}
+
}
 +
</syntaxhighlight>
  
 
== Iteration statements ==
 
== Iteration statements ==
Line 850: Line 1,005:
 
= Argument Checking =
 
= Argument Checking =
 
'''TO DO'''
 
'''TO DO'''
 +
 +
= nameof =
 +
''[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof The C# Way]''
 +
 +
<syntaxhighlight lang="C#">
 +
using System;
 +
using System.Diagnostics;
 +
 +
class Program
 +
{
 +
    static void Main()
 +
    {
 +
        int myIdentifier;
 +
        Debug.Assert(nameof(myIdentifier) == "myIdentifier");
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 +
''The D Way'' (Credit: https://forum.dlang.org/post/wcatxfktmqlgihzdmjtt@forum.dlang.org)
 +
 +
<syntaxhighlight lang="D">
 +
template nameOf(alias nameType)
 +
{
 +
    enum string nameOf = __traits(identifier, nameType);
 +
}
 +
 +
void main()
 +
{
 +
    int myIdentifier;
 +
    assert(nameOf!myIdentifier == "myIdentifier");
 +
}
 +
</syntaxhighlight>
  
 
= Resources =
 
= Resources =

Revision as of 23:03, 26 September 2019

Introduction: Hello world

The C# Way

file: hello.cs

using System;
class Hello
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello world!");
    }
}

The D Way

file: hello.d

import std.stdio;
void main(string[] args)
{
    writeln("Hello world!");
}

Things we have learnt so far:

  • standard file extension for D source code files is .d
  • we import a module instead of using a namespace;
  • methods (functions) can be top-level, that is, not declared inside a class; and called without being prefixed with an object/class or class name (e.g. writeln)
  • writeln is the D equivalent for C#'s System.Console.WriteLine;
  • some syntax is exactly the same as in C# (method definitions, string qualifiers, array declarations, comments)
  • many of the keywords are exactly the same (void,string);

Documentation Comments

The C# Way

 /// <summary>
 /// Documentation comment
 /// </summary>
 /// <param name="foo">...</param>
 /// <param name="bar">...</param>
 /// <returns>...</returns>

The D Way

 /**
  Documentation comment
 
  Params:  foo = param description
           bar = param description
 
  Returns: return value
 
  Throws:  Exception 
 */

Compilation

Simple compilation

The C# Way

With mcs mono compiler:

$ mcs source1.cs source2.cs -out:program.exe

The D Way

With DMD Digital Mars compiler:

$ dmd source1.d source2.d -ofprogram

Conditional Compilation Directives

The C# Way

Defines one or more conditional symbols (short: -d)

-define:S1[;S2]
// compile with: /define:xx
// or uncomment the next line:
//#define xx
 
[Test] public void define_test() {
    int x = 0;
    #if (xx)
       x = 5;
    #else
       x = 1;
    #endif
    Assert.AreEqual(x, 5);
}

The D Way

-version=S1
// compile with: -version=xx
// or uncomment the next line:
//version = xx
 
unittest {
    int x = 0;
    version(xx) {
       x = 5;
    }
    else {
       x = 1;
    }
    assert( x == 5 );
}

Coding styles

  • D programmers prefer to use the camelCase notation instead of PascalCase for method names, variable names and enum members
  • Module names (C# namespaces) are always in lowercase due to cross-platform compatibility regarding file names.
  • If there are conflicts between a named entity and a keyword, in C# you can use verbatim identifiers (@while). D does not have verbatim identifiers, but the convention is to add an underscore at the end of the entity (while_).


Type C# Way D Way
Class PascalCase PascalCase
Struct PascalCase PascalCase
class method PascalCase camelCase
class property PascalCase camelCase
enum declaration PascalCase PascalCase
enum value PascalCase camelCase
const UPPER_CASE -
local variable camelCase camelCase

Type system

Built-in types

Basic type names are very similar in both languages with the following differences:

  • The 8 bit signed integer from C# sbyte is written in D as byte;
  • The 8 bit unsigned integer from C# byte is written in D as ubyte;
  • There is no type equivalence for decimal
  • There are three types of char in D: char, wchar and dchar, each of them corresponding to an UTF encoding : UTF-8, UTF-16 and UTF-32. Since C# is using internally UTF-16 chars, the direct equivalent of C# char is D wchar.
  • There are also three types of string in D: string, wstring and dstring. The direct equivalent of C# string is in fact D wstring. These are not keywords in D, they are in fact declared as aliases to immutable char arrays.
  • There is another floating point type in D: real with no type equivalence in C#.
  • Complex floating point types Complex<T> are keywords in D: cfloat, cdouble, creal and they imaginary counterparts are ifloat, idouble, ireal;

Literals

TO DO

Arrays

Arrays in D are not too different than the ones form C# (for D see D Koans and dlang)

Static array

Initialize with a literal

The C# Way

string[] fruits = {"banana", "mango", "apple", "orange"};
// or long mode
string[] longFruits = new string[4]{"banana", "mango", "apple", "orange"};
Assert.AreEqual(fruits[0], "banana");
Assert.AreEqual(fruits.Length, 4);

The D Way

string[4] fruits = ["banana", "mango", "apple", "orange"];
assertEquals(fruits[0], "banana");
assertEquals(fruits.length, 4);

Initialize with same value

The C# Way

// no short way
int[] b = { 1, 1, 1}; // 3 elements with same value 1

The D Way

int[3] b = 1; // 3 elements with same value 1

Dynamic array

For D see D Koans and dlang

The C# Way

List<T> behaves similarly to a dynamic array

using System.Collections.Generic;
...
List<string> fruits = new List<string>{"banana", "mango"};
Assert.AreEqual(fruits.Count, 2);
 
fruits.Add("strawberry");
Assert.AreEqual(fruits.Count, 3);
Assert.AreEqual(fruits[2], "strawberry");

The D Way

string[] fruits = ["banana", "mango"];
assertEquals(fruits.length, 2);
 
fruits ~= "strawberry";
assertEquals(fruits.length, 3);
assertEquals(fruits[2], "strawberry");

Pointers

Since D is not a managed language, you are free to use pointers anywhere in the code, without encompassing them in an unsafe context. On the contrary, D code is by default unsafe, but you can force the safe context using the @safe keyword:

The C# Way

int value;
// here you can't use pointers
unsafe {
   int* p = &value;
}

The D Way

int value;
int* p = &value;
@safe {
   //here you can't use pointers
}

Delegates

Delegates in D are declared with the same keyword, but the return type precedes the declaration:

The C# Way

delegate int F

The D Way

int delegate(int x) Foo;
int function(int x) Foo;

Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that delegates are not multicast in D, therefore you cannot join or remove them.

Enums

There is no difference between enum declarations, except that so called C# flags enums are not necessarely decorated with the [Flags] attribute:

The C# Way

enum Option 
{ 
    Option1, 
    Option2
}
 
[Flags]
enum Permission
{
    read,
    write,
    all = read | wite
}

The D Way

The members of enums should be camelCased, so their first letter is lowercase. (see D Style

enum Color
{  
   red, 
   green, 
   blue
}
 
enum Permission
{
    read,
    write,
    all = read | wite
}

Struct

Struct are declared exactly the same way in D except:

  • There is no explicit layout in D. Nevertheless, there is a solution in the standard library.
  • Structs cannot implement interfaces

The C# Way

public struct TimeOfDay {
   public int hour;
   public int minute;
   public TimeOfDay(int h, int m) {
     hour = h;
     minute = m;
   }
}
...

var t1 = new TimeOfDay(8, 30);
Assert.AreEqual(t1.minute, 30);
 
// Declare a struct object without "new"
TimeOfDay t2;
t2.minute = 10;
Assert.AreEqual(t2.minute, 10);

The D Way

struct TimeOfDay {
   int hour;
   int minute;
}
... 
 
// preferred syntax
auto t1 = TimeOfDay(8, 30);  
assert(t1.hour == 8);
assert(t1.minute == 30);
 
// alternate C syntax, deprecated
TimeOfDay t2 = {9, 45};      
assert(t2.hour == 9);
 
// with designated initializers
auto t3 = {minute: 20, hour: 8};
assert(t3.hour == 8);
assert(t3.minute == 20);
  
auto t4 = TimeOfDay(10);     // not all members need to be specified
assert(t4.hour == 10);
assert(t4.minute == 0);

Union

D has unions, the equivalent of a C# struct with explicit layout where all fields offsets are 0

The C# Way

[StructLayout(LayoutKind.Explicit)]
struct MyUnion {
   [FieldOffset(0)]
   int someInt;
   [FieldOffset(0)]
   float someFloat;
}

The D Way

union MyUnion {
   int someInt;
   float someFloat;
}

Interfaces

Interfaces are declared in the same way as in C#, the only difference being that interfaces in D can have final methods, equivalent to a C# abstract class

The C# Way

interface I
{
    void Method();
}
 
abstract class C
{
    void Method()
    {
       Console.WriteLine("hello");
    }
}

The D Way

interface I
{
    void method();
}
 
interface C
{
    final void method()
    {
        writeln("hello");
    }
}

Classes

The C# Way

class A {
    private int myValue;  // not accessible from outside
                                                                                    
    public A(int startValue) { // constructor
       myValue = startValue;
    }
    public int Value {
       get { return myValue; }
    }
                                                                                    
}
                                                                                    
class B: A {
    public B(): base(3) {}
                                                                                    
    public int GetDoubleValue() {
       //return myValue * 2;  // Error: myValue is inaccessible due to its protection level
       return this.Value * 2;
    }
}
                                                                                    
[TestFixture] public class AboutClasses {
    [Test]public void inheritance() {
       B instance = new B();
       Assert.AreEqual(instance.GetDoubleValue(), 6);
    }
}

The D Way

class A {
   private int myValue;  // not accessible from outside
 
   this(int startValue) { // constructor
     myValue = startValue;
   }
}
 
class B: A {
   this() {
     super(3);   // call constructor on parent class A
   } 
 
   auto getDoubleValue() {
     return myValue * 2;  // B doesn't have this field but can access the value
   }
}
 
class AboutClasses {
  
   @Test public void inheritance() {
      auto instance = new B;
      assertEquals(instance.getDoubleValue(), 6);
    }
}

Generic Types

D is not using generics, instead it has a more powerful concept named templates.

The C# Way

public class A {
   public void Foo<T>(T arg) {}
}
// use
A a = new A();
a.Foo<int>(1);
 
 
public class C<T> where T: class { }
...                                           
C<A> c = new C<A>();
// C<int> i = new C<int>(); compiler error: int isn't a class
 
 
public class X { }
public class D<T> where T: X {}
 
D<X> d = new D<X>();
//D<A> d = new D<A>();compiler error: A isn't X

The D Way

void foo(T)(T arg) {}
// use
foo!int(3);
foo!string("bar");
 
class X {} 
class C(T) if is(T == class) {}
 
// use
C c = new C!X;
// C c = new C!int;  compiler error: int isn't a class
 
 
class D(T) if is(T : X) {}
auto d = new D!X;
//auto d = new D!(A);compiler error: A isn't X

Constraints

There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own.

The C# Way

class C<T> where T: new() {}

The D Way

class C(T) if (is(typeof(new T()) == T))

The template constraint will be read as if the result of expression new T() is of type T. If the T class has no constructor or is some other type, the new T() expression will result in an error or some other type, therefore the constraint will not be satisfied.

Events

There is no such concept in D language, but D programmers prefer to use signals and slots instead of events.

If you are keen to use events in D, a quick and dirty way to implement them can be found below (Credit: https://forum.dlang.org/post/dcdtuqyrxpteuaxmvwft@forum.dlang.org):

class Event(T...)
{
    alias CB = void delegate(T);
    CB[] callbacks;

    // NOTE: This implementation uses the `+` operator as it is likely more
    // familiar to C# programmers, but a more idiomatic D operator would be
    // `~`, D's concatenation operator.

    void opOpAssign(string op)(CB handler)
        if (op == "+" || op == "-")
    {
        static if (op == "+")
            callbacks ~= handler;
        else
        {
            import std.algorithm.mutation : remove;
            callbacks = callbacks.remove!(x => x == handler);
        }
    }

    void opOpAssign(string op)(void function(T) handler)
        if (op == "+" || op == "-")
    {
        import std.functional : toDelegate;
        opOpAssign!op(toDelegate(handler));
    }

    void opCall(T args)
    {
        foreach (cb; callbacks)
            cb(args);
    }
}

class Property(T)
{
    private T _value;

    alias event = Event!(T, T);

    auto changing = new event();
    auto changed = new event();

    T value() @property
    {
        return _value;
    }

    T value(T newValue) @property
    {
        if (_value != newValue)
        {
            T oldValue = _value;
            changing(_value, newValue);
            _value = newValue;
            changed(oldValue, _value);
        }
        return _value;
    }
}

void main()
{
    auto test = new Property!(int)();

    int changingCount;
    void onChanging(int oldValue, int newValue)
    {
        changingCount++;
    }
    test.changing += &onChanging;

    int changedCount;
    void onChanged(int oldValue, int newValue)
    {
        changedCount++;
    }
    test.changed += &onChanged;
    
    test.value = 1;  // value changed from 0 to 1
    test.value = 1;  // value not changed
    test.value = 2;  // value changed from 1 to 2

    assert(changingCount == 2);
    assert(changedCount == 2);
}

Dynamic Types

There is no such concept in D language. All types must be known at compile time but the same semantics can be simulated by forwarding.

Boxing

Boxing, otherwise known as wrapping, is the process of placing a primitive type within an object so that the primitive can be used as a reference object (wikipedia)

Value types in D are not boxed or unboxed automatically and do not inherit from ValueType. Also, you cannot implement interfaces for value types. Depending on your purpose, there are alternatives to boxing in D:

  • if you just want to have a container for several types you can use std.variant
  • if you want to write general purpose method with object parameters, use generics

The C# Way

[Test]public void boxing_test() {
    int a = 1;
    float b = 19.64f;
                                      
    object o = a;
    Assert.AreEqual(o, 1);
    Assert.AreEqual(tos(o), "1");
                                      
    o = b;
    Assert.AreEqual(o, 19.64f);
                                      
    b = (float)o;
    Assert.AreEqual(b, 19.64f);
    Assert.AreEqual(tos(o), "19.64");
}
private string tos(object obj) {
    return obj.ToString();
}

The D Way

import std.variant;

@Test void boxing_test() {
    int a = 1; 
    float b = 19.64f;
 
    Variant o = a;
    assert(o.type == typeid(int));
    assert(tos(o) == "1");
                                   
    v = b;
    auto x = v.get!(float);
    assert(x == 19.64f);
}
                                   
string tos(T)(T obj) {
    return to!string(obj);
}

Nullable types

Value types are not nullable in the D language. But there is a solution in the standard library module typecons - templated struct Nullable - to simulate this behavior. This is the same solution as in C#, although C# provides the shorthand syntax of "Type? variable-name", which can hide the usage of templated struct System.Nullable<>.

The C# Way

int? a = null; // short hand for: System.Nullable<int> a = null;
// attempting to use a.Value will cause an exception to be thrown
int? num = 10;
if (num.HasValue) {
    System.Console.WriteLine(3 * num.Value); // prints 30
} else {
    System.Console.WriteLine("num = Null");
}

The D Way

import std.typecons;

Nullable!int a; // a is null and cannot be used as an int until assigned a value
                // attempting to use a as an int will cause an exception to be thrown
Nullable!int num = 10;
if ( ! num.isNull) {
    writeln(num.get * 3);  // prints 30
    writeln(num * 3);      // prints 30 - get not required (see below)
} else {
    writeln("num = Null");
}
int y = num;  // get property not required due to "alias get this" inside Nullable for 
              // automatic compiler conversion to int when Nullable!(int) is not appropriate
int z = num.isNull ? int.init : num;
num.nullify();    // num set to null - cannot be used as an int until re-assigned

Enumerable types

In C#, a type can be considered enumerable if implements the IEnumerable interface. In D, a type is considered enumerable (Range is more used as the concept in D) if:

  • implements three methods named popFront, empty and front or,
  • implements a special operator named opApply;

The C# Way

class Prime: IEnumerable {
    object[] items = {1, 3, 5, 7};
                                                                           
    public IEnumerator GetEnumerator() {
       foreach (object o in items) {
          // Return the current element and then on next function call
          // resume from next element rather than starting all over again;
          yield return o;
       }
    }
}
 
[TestFixture] public class TestIterator {
    [Test] public void prime_test() {
       Prime primes = new Prime();
       int[] expected = {1, 3, 5, 7};
       int i = 0;
                                                                           
       foreach (object p in primes) {
          Assert.AreEqual(p, expected[i++]);
       }
    }
}

The D Way (front, popFront, empty)

class Prime {
    private int[] p = [1, 3, 5, 7];
    @property bool empty() {
       return p.length == 0;
    }
    @property int front() {
       return p[0];
    }
    void popFront() {
       p = p[1..$];
    }
} 

class TestIterator {
   mixin UnitTest;
 
   @Test prime() {
      Prime primes = new Prime();
      int[] expected = [1, 3, 5, 7];
      int i;
      for (; !primes.empty; primes.popFront()) {
         assert(primes.front == expected[i++]);
      }
   }
}

The D Way (opApply)

TO DO

Code Attributes

Property

The C# Way

class Person
{
    public Person()
    {
        Name = "Default Name";
    }
   
    public string Name { get; set; }
 }

The D Way

class Person 
{
    private string _name;
    
    this() 
    {
        _name = "Default Name";
    }
 
    @property string name()
    {
        return _name;
    }

    @property void name(string n)
    {
        _name = n;
    }
}

Statements

Declaring variables

The C# Way

int x;
string text = "abc";
const double PI = 3.14;

The D Way

int x;
string text = "abc";
enum double PI = 3.14;

Expressions

The C# Way

double average = (a + b) / 2; // assignment expression
foo.Bar(); // method call
 
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = new SomeStruct(); //struct initialization
 
List<int> list = new List<int>();
Dictionary<string, int> = new Dictionary<string, int>();

The D Way

double average = (a + b) / 2;  // assignment expression
foo.bar(); // method call
 
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = SomeStruct(); //struct initialization
 
List!int list = new List!int();
Dictionary!(string, int) = new Dictionary!(string, int);

Differences:

  • Structs in D are initialized without the new keyword.
  • Generic types in D are initialized using ! and by specifing types between parentheses. If there is only one specialization, parentheses can be omitted.

Using namespaces

D is importing modules instead of using namespaces. A collection of modules in D in named `package`. There is one-to-one correspondence between D modules and filenames and one-to-one correspondence between packages and folders.

The C# Way

using System;
using System.Collections;

The D Way

import system;
//this will try to import 'system.d' from the current folder. 
//if 'system' is a folder name, it will try to import a special file named 'package.d'
import system.collections;
//this will import the file 'collections.d' from a folder named 'system'.

if you are creating your own namespaces, the closest match for simulating C# namespaces is a trick used to import C++ code in D:

The D Way

extern (C++, MyNamespace) {
    class MyClass { ...}
}
//Fully qualified name of MyClass is MyNamespace.MyClass

if Statement

The if else statement has the same structure as in C#:

The C# Way

bool b; 
if (b) { ... }
 
int a;
if (a == 10) { .... } else { ... }
 
SomeClass c;
if (c == null) { ... }
void* p;
if (p != IntPtr.Zero) { ... }
 
string s = string.Empty;
if (string.IsNullOrEmpty(s)) { ... }

The D Way

bool b; 
if (b) { ... }
 
int a;
if (a == 10) { .... } else { ... }
if (a) { ... }
 
SomeClass c;
if (c is null) { ... }
if (!c) { ... }
 
void* p;
if (p) { ... }
 
string s = "";
if(s.length == 0) { ... }

Differences:

  • null values are compared using is operator. The is operator has a very different meaning in D.
  • In D, the conditional expression can be any other type than bool

switch Statement

The switch case statement has exactly the same structure as in C#, except that D provides a final switch statement intended to use with enum types.

The C# Way

enum Color { Blue, Red, Green }
 
Color c;
switch (c) {
    case Color.Blue: ... break;
    case Color.Red:  ... break;
    default: ... break;
}

The D Way

enum Color { Blue, Red, Green }
 
Color c;
final switch (c) {
    case Color.Blue: ... break;
    case Color.Red:  ... break;
    //compilation error if the switch statement does not treat all members of Color.
}

or using with

The D Way

// The members of enums should be camelCased
enum Color { blue, red, green }
 
Color c;
 
final switch() with (Color){
    case blue: ... break;
    case red:  ... break;
    case green:  ... break;
    //compilation error if the switch statement does not treat all members of Color.
}

Iteration statements

TO DO

Jump statements

TO DO

Exception handling

TO DO

Argument Checking

TO DO

nameof

The C# Way

using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        int myIdentifier;
        Debug.Assert(nameof(myIdentifier) == "myIdentifier");
    }
}

The D Way (Credit: https://forum.dlang.org/post/wcatxfktmqlgihzdmjtt@forum.dlang.org)

template nameOf(alias nameType)
{
    enum string nameOf = __traits(identifier, nameType);
}

void main()
{
    int myIdentifier;
    assert(nameOf!myIdentifier == "myIdentifier");
}

Resources