Programming in D for CSharp Programmers

From D Wiki
Jump to: navigation, search

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