Vala for C# Programmers

This page describes the syntax differences, not the similarities between Vala and C#. It is intended to be a quick introduction to Vala for programmers who already know C#.

Source Files

C#: *.cs

Vala: *.vala

Compilation

C# (.NET / Mono): compiled to CIL

> csc source1.cs source2.cs /out:program.exe
$ gmcs source1.cs source2.cs -out:program.exe

Vala: compiled to native code via C code as intermediate code

$ valac source1.vala source2.vala -o program

Vala's standard object system is GObject, compiled Vala libraries are valid C libraries.

Using Packages

Mono: -pkg:

$ gmcs source.cs -pkg:gtk-sharp-2.0 -out:program.exe

Vala: --pkg

$ valac source.vala --pkg gtk+-2.0 -o program

Naming Conventions

C#

  • classes, structs, delegate types: CamelCase

  • methods, properties, events: CamelCase

  • local variables, fields: mixedCamelCase (sometimes lower_case)

  • constants, enum values: CamelCase

Vala

  • classes, structs, delegate types: CamelCase

  • methods, properties, signals: lower_case

  • local variables, fields: lower_case

  • constants, enum values: UPPER_CASE

Main Entry Point

C#: Main, must be inside a class

Vala: main, may be outside a class

System Namespace

C#: most important namespace is System, not imported by default

Vala: most important namespace is GLib, implicitly imported by default

Value Types

  • sizes of standard types (int, long, ...) are architecture dependent

  • additional Vala types int8, int16, int32, int64 (signed), uint8, uint16, uint32, uint64 (unsigned) with architecture independent guaranteed sizes

  • no byte, sbyte (use uint8, int8 instead)

  • no decimal

  • C# char is UCS-2, not Vala's char, but similar to Vala's UCS-4 unichar

Verbatim String Literals

C#: @"verbatim string"

Vala: """verbatim string"""

Documentation Comments

C#:

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

Vala: Valadoc comments

/**
 * Documentation comment
 *
 * @param foo ...
 * @param bar ...
 * @return ...
 */

Object Base Class

C#: implicit inheritance from object (System.Object)

class Foo
{
    // ...
}

Vala: no implicit inheritance from Object (GLib.Object)

class Foo : Object {
    // ...
}

What happens if you don't inherit from Object? Nothing terrible. These classes will be slightly more lightweight, however, they will lack some features such as property change notifications, and your objects won't have a common base class. Usually inheriting from Object is what you want.

Method Overloading

C#

class Demo
{
    public void Draw(string text) { }

    public void Draw(Shape shape) { }


    /* Method overloading + chaining for convenience methods with less arguments */

    void F(int x, string s, double z) { }

    void F(int x, string s)
    {
        F(x, s, 0.5);
    }

    void F(int x)
    {
        F(x, "hello");
    }
}

Vala: no method overloading, use different names instead or default values for arguments

class Demo : Object {

    public void draw_text (string text) {
    }

    public void draw_shape (Shape shape) {
    }

    /* Argument default values, available in Vala, planned for C# 4.0 */
    void f (int x, string s = "hello", double z = 0.5) {
    }
}

Multiple Constructors

C#: constructor overloading

class Foo
{
    public Foo() { }
    public Foo(int foo) { }
    public Foo(string bar) { }
}

new Foo();
new Foo(42);
new Foo("hello");

Vala: no constructor overloading, named constructors instead

class Foo : Object {
    public Foo () { }
    public Foo.with_foo (int foo) { }
    public Foo.from_bar (string bar) { }
}

new Foo ();
new Foo.with_foo (42);
new Foo.from_bar ("hello");

Constructor Chaining

Base Constructor Chain-Up

C#

class Foo : Bar
{
    public Foo() : base(42)
    {
        // ...
    }
}

Vala: base call inside constructor

class Foo : Bar {
    public Foo () {
        base (42);
        // ...
    }
}

Multiple Constructor Chaining

C#

class Foo
{
    public Foo() : this("bar") { }
    public Foo(string bar) { }
}

Vala

class Foo : Object {
    public Foo () {
        this.with_bar ("bar");
    }

    public Foo.with_bar (string bar) {
    }
}

Delegates / Lambdas

C#

delegate void DelegateType(string s);

void Method(string s)
{
    Console.WriteLine(s);
}

// Original style
DelegateType d1 = new DelegateType(Method);

// Direct method assignment
DelegateType d2 = Method;

// C# 2.0 style
DelegateType d3 = delegate(string s) => { Console.WriteLine(s); };

// Lambda Expression with types (C# 3.0)
DelegateType d4 = (string s) => { Console.WriteLine(s); };

// Lambda Expression without types (C# 3.0)
DelegateType d5 = (s) => { Console.WriteLine(s); };

Vala: either lambda expression without types or direct method assignment (without new ...), no C# 2.0 style

delegate void DelegateType (string s);

void method (string s) {
    stdout.printf ("%s\n", s);
}

DelegateType d1 = method;
DelegateType d2 = (s) => { stdout.printf ("%s\n", s); };

Events

C#: events

using System;

delegate void SomeEventHandler(object sender, int i);

class Foo
{
    public event SomeEventHandler SomeEvent;

    public void RaiseSomeEvent(int i)
    {
        if (SomeEvent != null) SomeEvent(this, i);
    }
}

class Demo
{
    static void OnSomeEvent(object sender, int i)
    {
        Console.WriteLine("Handler A: " + i);
    }

    static void Main()
    {
        var foo = new Foo();
        foo.SomeEvent += OnSomeEvent;
        foo.SomeEvent += (s, i) => Console.WriteLine("Handler B: " + i);
        foo.RaiseSomeEvent(42);
        foo.SomeEvent -= OnSomeEvent;
    }
}

Vala: signals

class Foo {
    public signal void some_event (int i);
}

class Demo {
    static void on_some_event (Foo sender, int i) {
        stdout.printf ("Handler A: %d\n", i);
    }

    static void main () {
        var foo = new Foo ();
        foo.some_event.connect (on_some_event);
        foo.some_event.connect ((s, i) => stdout.printf ("Handler B: %d\n", i));
        foo.some_event (42);
        foo.some_event.disconnect (on_some_event);
    }
}

No extra delegate declaration, signals can be emitted directly (no null checking necessary). Use .connect() and .disconnect() instead of += and -=. Both is possible in Vala, however += and -= may become deprecated for signal connection.

Signals do not support add {} and remove {} blocks.

Interfaces

C#

interface IFoo
{
    void Foo(int i);
    int Bar(string s, double d);
}

Vala: public abstract necessary

interface Foo {
    public abstract void foo (int i);
    public abstract int bar (string s, double d);
}

Why? Because Vala interfaces may have non-abstract methods (i.e. methods with implementations) and private methods! This means Vala interfaces can be used as mixins (restricted form of multiple inheritance).

'I'-prefix not common in GObject world but allowed

C#: interface inheritance

interface IfaceA
{
    void MethodA();
}

interface IfaceB : IfaceA
{
    void MethodB();
}

class Demo : IfaceB
{
    public void MethodA() { }
    public void MethodB() { }
}

Vala: interface prerequisites

interface IfaceA : Object {
    public abstract void method_a ();
}

interface IfaceB : Object, IfaceA {
    public abstract void method_b ();
}

class Demo : Object, IfaceA, IfaceB {
    public void method_a () { }
    public void method_b () { }
}

Interfaces in Vala may not inherit from other interfaces, but they may declare other interfaces to be prerequisites, which works in roughly the same way. Interfaces may also have a class as a prerequisite. This is often used to ensure that an instance of an interface is also an Object subclass. The fact that interfaces can not inherit from other interfaces is mostly only a technical distinction - in practice Vala's system works the same as C# in this area, but with the extra feature of prerequsite classes.

Enums

Vala enums may have methods:

enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;

    public bool is_hot () {
        return this == SUMMER;
    }
}

In C# this can only be achieved by extension methods:

enum Season { Spring, Summer, Autumn, Winter }

static class SeasonExtensions
{
    public static bool IsHot(this Season season)
    {
        return season == Season.Summer;
    }
}

Struct Initialization

C#

var p1 = new Point();
var p2 = new Point() { X = 2, Y = 3 };
Point p3;
p3.X = 2;
p3.Y = 3;

Vala: structs are instantiated without using the new operator.

var p1 = Point ();
var p2 = Point () { x = 2, y = 3 };
Point p3 = { 2, 3 };

Vala structs must be initialized before first use. A Vala struct cannot implement interfaces.

Multi-Dimensional Arrays

C#: both rectangular [,] and jagged [][] multi-dimensional arrays

Vala: only rectangular [,] multi-dimensional arrays, jagged array support planned

Nullable Types

C#: mark nullable value types

int? i = null;

Vala: mark nullable reference type arguments and return values of methods. They are non-nullable by default!

Foo? method (Foo? foo, Bar bar) {
    return null;
}

In this example: foo and return value may be null, bar must be non-null. Checked at run-time, and to some extent at compile time.

Conclusion: same syntax (? type modifier), different meanings.

Code Attributes

C#: self-definable

Vala: built into the compiler, mostly used for bindings or D-Bus interfaces. The most prominent attribute for bindings is [CCode (...)].

Properties

Default Values for Auto-Implemented Properties

C#

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

    public string Name { get; set; }
}

Vala

class Person : Object {
    public string name { get; set; default = "Default Name"; }
}

Setting in constructor works as well, of course.

Property Change Notifications

C#: implement INotifyPropertyChanged

using System.ComponentModel;

class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    protected void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    static void Main()
    {
        var person = new Person();
        person.PropertyChanged += (sender, e) =>
        {
            System.Console.WriteLine("Property '{0}' changed", e.PropertyName);
        };
        person.Name = "Foo";
        person.Name = "Bar";
    }
}

Vala: connect to notify signal

Every instance of a class derived from Object has a signal called notify. This signal gets emitted every time a property of its object changes.

class Person : Object {
    public string name { get; set; }
}

void main () {
    var person = new Person ();
    person.notify.connect ((sender, property) => {
        stdout.printf ("Property '%s' changed\n", property.name);
    });
    person.name = "Foo";
    person.name = "Bar";
}

If you're only interested in change notifications of a single property you can use this syntax:

person.notify["name"].connect ((sender, property) => {
    stdout.printf ("name has changed\n");
});

Note that in this case you must use the string representation of the property name where underscores are replaced by dashes: my_property_name becomes "my-property-name" in this representation, which is the GObject property naming convention.

Change notifications can be disabled with a CCode attribute tag immedietely before the declaration of the property:

class MyObject : Object {

    // notify signal is NOT emitted upon changes in the property
    [CCode (notify = false)]
    public int without_notification { get; set; }

    // notify signal is emitted upon changes in the property
    public int with_notification { get; set; }
}

Exceptions

C#: unchecked exceptions, class-based

[Serializable()]
public class MyException : System.Exception
{
    public MyException() { }
    public MyException(string message) { }
    public MyException(string message, System.Exception inner) { }
    protected MyException(System.Runtime.Serialization.SerializationInfo info,
                          System.Runtime.Serialization.StreamingContext context) { }
}

void Method()
{
    throw new MyException("not enough foo");
}

try {
    Method();
} catch (MyException e) {
    Console.Error.WriteLine(e.Message);
}

Vala: checked exceptions, Vala terminology: errors, not class-based, no wrapping

// error domain with multiple error codes instead of exception class
errordomain MyError {
    FOO,
    BAR
}

// must be declared in method signature, part of the contract
void method () throws MyError {
    // error domain, error code, error message
    throw new MyError.FOO ("not enough foo");
}

// must be catched or propagated, compiler warning if ignored
try {
    method ();
} catch (MyError e) {
    stderr.printf ("Error: %s\n", e.message);
}

Although the compiler emits warnings for ignored errors it does not abort the compilation process. This allows prototyping without proper error handling and will hopefully prevent forgotten empty catch blocks.

Argument Checking

C#

void Method(double d, int i, Foo foo)
{
    if (d < 0 || d > 1.0)
        throw new ArgumentOutOfRangeException();
    if (i < 0 || i > 10)
        throw new ArgumentOutOfRangeException();
    if (foo == null)
        throw new ArgumentNullException();
    // ...
}

Vala: reference type parameters are implicitly checked for null unless they are marked nullable with ?, so you don't have to check them manually. Methods may have preconditions:

void method (double d, int i, Foo foo)
     requires (d >= 0.0 && d <= 1.0)
     requires (i >= 0 && i <= 10)
{
    // ...
}

Vala additionally supports postcontditions for checking the return value:

int square (int i)
    ensures (result >= 0)
{
    return i * i;
}

result is a special variable representing the return value.

Use exceptions (errors) for recoverable runtime errors (database errors, I/O errors), use preconditions and assertions (assert (...)) for programming errors such as illegal arguments.

Unsafe Code and Pointers

C#: must be inside unsafe { } block

Vala: may be used everywhere. However, be careful nonetheless.

Conditional Compilation Directives

C#: compiler options -define: or -d:

Vala: compiler options --define or -D

no #define, #undef, #region, #endregion, #line

Resource Disposing

C#: destructors are non-deterministic, implement IDisposable instead, resource control with using

class MyResource : IDisposable
{
    public void Dispose()
    {
        // ...
    }
}

/* Usage: */
using (var res = new MyResource()) {
    // ...
}

Vala: destructors are deterministic, you can implement the RAII pattern with destructor

class MyResource : Object {
    ~MyResource () {
        // ...
    }
}

/* Usage: */
{
    var res = new MyResource ();
    // ...
}

Resource is disposed as soon as it goes out of scope.

Memory Management

C#: Garbage collection

Vala: Automatic reference counting

This has both advantages and disadvantages. Reference counting is deterministic, but you can form reference cycles in some cases. In these cases you must use weak references in order to break those cycles. The Vala keyword for this is weak.

See Vala's Memory Management Explained

Asynchronous Calls

C#

using System;

class AsyncDemo
{
    delegate int BinaryOperator(int a, int b);

    static int Adder(int a, int b)
    {
        return a + b;
    }

    static void Callback(IAsyncResult r)
    {
        BinaryOperator adder = (BinaryOperator) r.AsyncState;

        Console.WriteLine("Addition completed");
        Console.WriteLine("Result was: {0}", adder.EndInvoke(r));
    }

    static void Main()
    {
        BinaryOperator adder = Adder;

        adder.BeginInvoke(4, 5, Callback, adder);

        /* wait */
        Console.ReadLine();
    }
}

Vala: built-in support for asynchronous methods (async, yield), must be compiled with --pkg gio-2.0

class AsyncDemo {

    static async int adder (int a, int b) {
        return a + b;
    }

    static async void start () {
        int sum = yield adder (4, 5);
        stdout.printf ("Addition completed\n");
        stdout.printf ("Result was: %d\n", sum);
    }

    static void main () {
        start ();

        /* wait */
        var loop = new MainLoop (null, false);
        loop.run ();
    }
}

Static Constructors

C#: called before the first instance is created or any static members are referenced

class Foo
{
    static Foo()
    {
        System.Console.WriteLine("Static constructor invoked.");
    }
}

Vala: static construct { } block. The first time that a class, or any subclass of it, is instantiated, this code is run. It is guaranteed that this code will run exactly once in a program where such a class is used.

class Foo : Object {
    static construct {
        stdout.printf ("Static constructor invoked.\n");
    }
}

Additionally a Vala class can have a class construct { } block. This block will be executed once at the first use of its class, and once at the first use of each subclass of this class.

External Methods

C#

using System;
using System.Runtime.InteropServices;

public class MainClass
{
    [DllImport("libfoo.so")]
    public static extern int SampleMethod(int x);

    static void Main()
    {
        Console.WriteLine("SampleMethod() returns {0}", SampleMethod(5));
    }
}

Vala: CCode attribute, specify original function name (only if it differs from the Vala name)

public class MainClass : Object {

    [CCode (cname = "SampleMethod")]
    public static extern int sample_method (int x);

    static void main () {
        stdout.printf ("sample_method () returns %d\n", sample_method (5));
    }
}

Pass library name to the compiler with -X -l... (with -X meaning the next option is passed through to the C compiler)

$ valac demo.vala -X -lfoo

You can also pass a C source file containing the external method to the Vala compiler. This allows for mixed Vala and C source code projects.

$ valac demo.vala foo.c

Reflection

Reflection support in Vala is limited. Vala provides run-time type information for its data types (is operator, foo.get_type (), dynamic casting via as operator). For example, you can get the names of types, of signals and properties of a class and of enum values.

class Foo : Object {
    public int hello { get; set; }
    public int world { get; set; }
    public int foo_bar { get; set; }

    public signal void action ();
    public signal void more_action ();
}

enum Bar {
    FEE, FIE, FOE, FUM
}

void main () {

    /* Getting type information */
    Type type = typeof (Foo);
    stdout.printf ("%s\n", type.name ());

    /* Instantiation from type */
    Foo foo = (Foo) Object.new (type);

    /* list properties of a class */
    var obj_class = (ObjectClass) typeof (Foo).class_ref ();
    var properties = obj_class.list_properties ();
    foreach (var prop in properties) {
        stdout.printf ("%s\n", prop.name);
    }

    /* enum value as string */
    var enum_class = (EnumClass) typeof (Bar).class_ref ();
    string name = enum_class.get_value (Bar.FEE).value_name;
    stdout.printf ("Enum value as string: %s\n", name);

    /* list signals of a class */
    uint[] ids = Signal.list_ids (typeof (Foo));
    foreach (uint id in ids) {
        stdout.printf ("%s\n", Signal.name (id));
    }
}

Libraries built with GObjectIntrospection support can be fully introspected.

Not Available

  • No LINQ (not planned for 1.0, maybe later) there are one [https://gitlab.com/kosmospredanie/gpseq|realization]

  • No operator overloading (vala-list)

  • No method or constructor overloading (use different method names / named constructors instead, as described above)
  • No extension methods
  • No constraints on generic type parameters (i.e. no where)

  • No generic delegates
  • No conversion operators (i.e. no explicit and implicit)

  • No partial classes and methods

  • No sealed classes (planned)

  • No static classes (use nested namespaces instead. Vala supports namespace methods, they are implicitly static)
  • No goto, no labeled statements

  • No constructor initializers
  • No bounds checking for arrays (optional support planned)
  • No checked, unchecked, fixed, stackalloc, readonly

Collections

C#: System.Collections.Generic namespace

Vala: Gee namespace, --pkg gee-1.0, http://live.gnome.org/Libgee

Rough equivalents:

System.Collections.Generic

Gee

Classes

Dictionary

HashMap

HashSet

HashSet

LinkedList

LinkedList

List

ArrayList

Queue

LinkedList, PriorityQueue

SortedDictionary

TreeMap

Stack

LinkedList

Interfaces

ICollection

Collection

IComparer

Comparable

IDictionary

Map

IEnumerable

Iterable

IEnumerator

Iterator

IList

List

Queue

Deque

See Gee Examples

You can access and assign Gee collection items via indexers (e.g. my_map[key] is equivalent to my_map.get (key)). Vala supports an in operator for collections: x in my_collection is equivalent to my_collection.contains (x). This operator also works with strings, even though strings are not collections.

Please note that Libgee checks for errors like the bounds for the index key with an assert, and so it won't raise any catchable SystemException like in C#.

Indexers

C#: usage of this keyword to define indexers

class SampleCollection<T>
{
    private T[] arr = new T[100];

    public T this[int i]
    {
        get { return arr[i]; }
        set { arr[i] = value; }
    }
}

class IndexerDemo
{
    static void Main(string[] args)
    {
        var stringCollection = new SampleCollection<string>();

        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}

Vala: implement T get(int i) and void set(int i, T item) methods

class SampleCollection<T> {

    private T[] arr = new T[100];

    public T get (int i) {
        return arr[i];
    }

    public void set (int i, T item) {
        arr[i] = item;
    }
}

void main (string[] args) {
    var string_collection = new SampleCollection<string> ();

    string_collection[0] = "Hello, World";
    stdout.printf ("%s\n", string_collection[0]);
}

IO, Network Sockets

C#: System.IO, System.Net.Sockets namespaces

Vala: GLib namespace (imported by default), --pkg gio-2.0, GIO is part of GLib

See GIO Examples, GIO Networking Examples

Console Input / Output

C#

System.Console.WriteLine("Hi!");
System.Console.Write("Please enter your name: ");
var name = System.Console.ReadLine();
System.Console.WriteLine("Welcome, {0}!", name);

Vala

stdout.printf ("Hi!\n");
stdout.printf ("Please enter your name: ");
var name = stdin.read_line ();
stdout.printf ("Welcome, %s!\n", name);

printf uses the same format specifiers as the C function.

GTK+ Demo App

C#

using Gtk;

class Demo : Window
{
    public Demo() : base("This is a window")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);

        DeleteEvent += delegate { Application.Quit(); };

        var button = new Button("Click");
        Add(button);
        ShowAll();
    }

    static void Main()
    {
        Application.Init();
        new Demo();
        Application.Run();
    }
}

Vala

using Gtk;

class Demo : Window {

    public Demo () {
        this.title = "This is a window";
        set_default_size (250, 200);
        set_position (WindowPosition.CENTER);

        this.destroy.connect (Gtk.main_quit);

        var button = new Button.with_label ("Click");
        add (button);
        show_all ();
    }

    static void main (string[] args) {
        Gtk.init (ref args);
        new Demo ();
        Gtk.main ();
    }
}

Vala's GTK+ API is very close to the original GTK+ API. In fact, you're using GTK+ functions directly, only with different syntax.

See GTK+ Examples

Bindings

Mono: runtime bindings (wrappers), GAPI tools, .sources files with XML syntax

Vala: no runtime bindings necessary, Vala method calls are direct C function calls, mapped by *.vapi (Vala API) files with Vala syntax, annotated with attributes like [CCode (...)]

On Unix systems VAPI files are usually installed in

  • /usr/share/vala/vapi/ or

  • /usr/local/share/vala/vapi/

Using a vapi file (e.g. foo-1.0.vapi):

$ valac source.vala --pkg foo-1.0

The Vala compiler will additionally look for a corresponding pkg-config file (*.pc) with the same base name (e.g. foo-1.0.pc), usually located in

  • /usr/lib/pkgconfig/ or

  • /usr/local/lib/pkgconfig/

and pass its configuration to the C compiler if existing.

VAPI files are either auto-generated for GObject libraries or hand-written for non-GObject libraries.

Projects/Vala/ValaForCSharpProgrammers (last edited 2019-08-13 00:23:28 by Gavr Maxutenko)