Skip to content

C# Type System and Conversions

by Alex Peck on July 15th, 2009

This is really a catch all post for the things I missed when reviewing the fundamentals. Skeet describes the C# type system as static, explicit and safe.

Widening and narrowing conversions

A narrowing conversion is a conversion which loses precision, for example converting a double to an int is a narrowing conversion. In C#, you can usually only perform narrowing conversions explicitly. Widening conversions, which preserve precision, may be performed implicitly.

Implementing conversion: Overview

.NET 2.0 introduced the TryParse family of methods, which in addition to the ToString and Parse methods, may be used to convert types to and from strings. Obviously it is possible to cast between types, but only between types that define conversion operators. In summary, you can implement conversion using these methods:

  • Conversion operators (operator overloading for for implicit/explicit casts)
  • Override object ToString and Parse methods
  • Implement IConvertible
  • Implement a TypeConverter. This is not in scope for the foundation course.

There’s nothing mysterious about conversion operators or overriding object methods, just remember to use implicit for widening conversions and explicit for narrowing conversions. Here’s a zero utility example:

public struct MyConvertible
{
    int value;
 
    public MyConvertible(int i)
    {
        value = i;
    }
 
    public static explicit operator MyConvertible(long l)
    {
        return new MyConvertible((int)l);
    }
 
    public static implicit operator long(MyConvertible c)
    {
        return c.value;
    }
 
    public override string ToString()
    {
        return this.value.ToString();
    }
}

Implementing IConvertible

Clearly, it would not be possible to implement every method in IConvertible. It’s best to just add the IConvertible interface to your type, then use Visual Studio generate method stubs. Either implement methods, or throw an InvalidCastException if there is no meaningful conversion. Personally I would prefer Visual Studio to give me this by default instead of NotImplementedException.

Co-variance and contra-variance

This is really a diversion from the .NET foundation course, but it is worth understanding properly. For an in depth view, please refer to Eric Lippert’s variance series, part 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 to infinity. Eric uses the terms bigger and smaller to refer to what I think of as generalised and specialised types.

For example, in a hierarchy of shapes, rooted at Shape, you could have two branches, one consisting of Polygon and Triangle, the other consisting of Ellipse and Circle. Clearly, a triangle is a specialised version of Shape, and Shape is a generalised version of Triangle. Circle is neither a specialisation or generalisation of Triangle.

To quote Wikipedia a type conversion operator is:

  • covariant if it preserves the ordering, ≤, of types, which orders types from more specific to more generic;
  • contravariant if it reverses this ordering, which orders types from more generic to more specific;
  • invariant if neither of the above apply.

That’s a bit wordy. To convince myself that I understand what it means in the context of C# I tried to think of an example of each. In C#, let’s say we do something like Shape s = new Triangle(). This assignment is a covariant operation: you are assigning a specialised reference to a more general reference.

If you tried to write something like this: List<Shape> shapes = new List<Triangle>() the C# compiler will complain. This is because generics are invariant, the types List<Shape> and List<Triangle> are not related.

Contravariance is tricker to present in a byte size example, consider this fragment:

class Contravariant
{
    public delegate void MyDelegate(Triangle value);
 
    void Example()
    {
        MyDelegate shapeDelegate = ShapeMethod;
        MyDelegate triangleDelegate = TriangleMethod;
 
        Triangle t = new Triangle();
        shapeDelegate(t);
    }
 
    public static void ShapeMethod(Shape value)
    {
    }
 
    public static void TriangleMethod(Triangle value)
    {
    }
}

The delegate parameter type is contravariant. At first this might look a little strange. But inside ShapeMethod we only care about shapes, so invoking shapeDelegate with a Triangle is fine, because it is just treated as a Shape.

No comments yet

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS