#1,199 – Equality and Inequality with Nullable Types

The equality and inequality operators work with nullable types as follows:

  • If both operands are non-null, the operators are applied to the values
  • If one operand is null and the other has a value, == returns false, != returns true
  • If both operands are null, == returns true and != returns false
            int? val1 = 5;
            int? val2 = 10;
            int? val3 = null;
            int? val4 = null;
            int? val5 = 10;

            bool b1 = val1 == val2;   // False
            b1 = val2 == val5;        // True
            b1 = val1 == val3;        // False
            b1 = val1 != val3;        // True
            b1 = val3 == val4;        // True
            b1 = val3 != val4;        // False

#1,198 – Using Operators with Nullable Types

When using a nullable type, you can use the operators with the nullable type that are associated with the underlying value type.

The example below shows how we can use the < and > operators with the int? type.  They work as they would work with the int type itself, for non-null values.

            int? val1 = 5;
            int? val2 = 10;

            bool b1 = val1 < val2;   // True

If one of the two values is null, the expression will evaluate to false, regardless of the other value.

            val2 = null;
            b1 = val1 < val2;    // False
            b1 = val2 < val1;    // False
            b1 = val1 == val2;   // False

#1,111 – Converting an Integer to a String in a Different Base

You can convert an integer-based value to a string using the ToString method on the integer.  This results in a string representing the integer as a base 10 number.

            int i = 42;
            // ToString() called implicitly
            Console.WriteLine(i);  // base 10

1111-001

You can also convert integer-based values to strings that represent the number in base 2 (binary), 8 (octal), or 16 (hexadecimal).  You use the Convert.ToString method, passing it the number to convert and the base.

            int i = 42;
            int i2 = 1964;
            int i3 = -128;

            Console.WriteLine("{0} dec, {1} hex, {2} oct, {3} bin",
                i,
                Convert.ToString(i, 16),
                Convert.ToString(i, 8),
                Convert.ToString(i, 2));

            Console.WriteLine("{0} dec, {1} hex, {2} oct, {3} bin",
                i2,
                Convert.ToString(i2, 16),
                Convert.ToString(i2, 8),
                Convert.ToString(i2, 2));

            Console.WriteLine("{0} dec, {1} hex, {2} oct, {3} bin",
                i3,
                Convert.ToString(i3, 16),
                Convert.ToString(i3, 8),
                Convert.ToString(i3, 2));

1111-002

#1,076 – Implicit Numeric Conversions from the char Type

You can implicitly convert an instance of a char to any of the following numeric types:

ushort int, uint, longulong, float, double, or decimal

When you implicitly convert a char to a numeric value, you use the associated code point as the numeric value.  Because of this implicit conversion, you can use char instances within numeric expressions.  Below are some examples:

            int n = 'a';  // 97
            char c1 = 'a';
            n = c1;  // still 97

            char c2 = '\u1820';
            int n2 = c2;  // 6176 (0x1820)

            int delta = 'd' - 'a';  // 3
            int strangeSum = 'a' + 'b';  // 97 + 98 = 195

#1,066 – Constraining a Type Parameter on a Generic Interface

You can constrain a type parameter on a generic interface so that it can be used only as the output type of methods in the interface and not as a type of any method parameters.  You do this using the out keyword on the type parameter.

        public interface FirstAndLast<out T>
        {
            T First();
            T Last();
            void AddThing(T item);   // won't compile
        }

This seems like an odd thing to do, but ensuring that a particular type parameter is used only as output allows us to later use this generic interface covariantly.  (I’ll explain this in an upcoming post).

#1,065 – Cases When Array Covariance Doesn’t Work

Array covariance in C# allows you to assign an array of objects of a more derived type to an array of objects of a base type.  An array of type B can be assigned to an array of type A is type B is implicitly convertible to A.  More specifically, an implicit reference conversion from B to A must exist.

This restriction means that array covariance will not work for implicit conversions that are not implicit reference conversions.  Specifically, arrays are not covariant for implicit numeric conversions, boxing conversions or implicit custom conversions.

            // Covariance does work when implicit reference conversion
            // exists for underlying types
            //   - From S to T when S derives from T
            //   - From S to object or dynamic
            //   - From S to interface-type T when S implements T
            //   - From interface-type S to interface-type T when S derives from T
            // All examples below work (compile)
            Terrier[] tarr1 = new Terrier[2];
            Dog[] darr1 = tarr1;
            object[] oarr1 = tarr1;
            dynamic[] dynarr1 = tarr1;
            Dog[] darr2 = new Dog[2];
            IBark[] ibarr1 = darr2;
            IBarkBetter[] ibb1 = new IBarkBetter[2];
            IBark[] ibarr2 = ibb1;

            // Covariance does NOT work with implicit numeric conversions
            //  e.g. byte to short
            byte[] barr1 = new byte[2];
            short[] sarr1 = barr1;  // Compile-time error

            // Covariance does NOT work with implicit nullable conversions
            int[] inarr1 = new int[2];
            int?[] innarr1 = inarr1;  // Compile-time error

            // Covariance does NOT work with implicit boxing conversions
            int[] inarr2 = new int[2];
            object[] oarr2 = inarr2;  // Compile-time error

            // Covariance does NOT work with implicit custom conversions
            Cow c = new Cow("Bessie");
            Dog d = c;  // Implicit Cow to Dog works (custom conversion)
            Cow[] herd = new Cow[2];
            Dog[] pack = herd;  // Compile-time error

#1,064 – Getting Around Inability to Explicitly Convert Type Parameters

You can’t explicitly convert a type parameter to either a value type or a reference type (you can convert to an interface).

To get around this restriction, you can use the as operator on the type parameter.  This gives you a way to effectively convert the type parameter, get the code to compile, and avoid runtime exceptions.

    class Program
    {
        public class ThingContainer<T>
        {
            private T thing;

            public void SetThing(T t)
            {
                thing = t;

                // Won't compile
                //int i = (int)t;

                // Do this instead
                int? i = t as int?;
                if (i.HasValue)
                    Console.WriteLine("Your int: " + i);

                // Won't compile
                //Dog d = (Dog)t;

                // Do this instead
                Dog d = t as Dog;
                if (d != null)
                    Console.WriteLine("Your Dog: " + d.Name);
            }
        }

        static void Main(string[] args)
        {
            ThingContainer<int> intcont = new ThingContainer<int>();
            intcont.SetThing(5);

            ThingContainer<Dog> dogcont = new ThingContainer<Dog>();
            dogcont.SetThing(new Dog("Bowser"));

            ThingContainer<Cow> cowcont = new ThingContainer<Cow>();
            cowcont.SetThing(new Cow("Bessie"));

            Console.ReadLine();
        }
    }

1064-001

#1,063 – Explicit Conversions and Type Parameters

You can’t explicitly convert a type parameter to either a reference type or a value type.  Casts to reference or value types are disallowed at compile-time.  The compiler allows casting the type parameter to an interface type and this conversion will succeed at run-time if the object implements that interface.

        public class ThingContainer<T>
        {
            private T thing;

            public void SetThing(T t)
            {
                thing = t;

                // Won't compile
                int i = (int)t;

                // Won't compile
                Dog d = (Dog)t;

                // Will compile, but throw
                // InvalidCastException at run-time
                // if T doesn't implement IBark
                IBark ib = (IBark)t;
            }
        }

        static void Main(string[] args)
        {
            ThingContainer<Dog> dogcont = new ThingContainer<Dog>();
            dogcont.SetThing(new Dog("Bowser"));

            ThingContainer<Cow> cowcont = new ThingContainer<Cow>();
            cowcont.SetThing(new Cow("Bessie"));

            Console.ReadLine();
        }

#1,062 – Unboxing Conversions

If you’ve converted a value-typed object to a reference-typed object by boxing it, you can later unbox the object, converting it back to a value type.  Unboxing is an explicit conversion and requires a cast operator.

Below are some examples of unboxing conversions.

        public interface IArea
        {
            double CalcArea();
        }

        public struct MyPoint : IArea
        {
            public double X;
            public double Y;

            public MyPoint(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double CalcArea()
            {
                return X * Y;
            }
        }

        static void Main(string[] args)
        {
            int i1 = 12;
            object o = i1;  // Boxing - implicit

            // Unbox from object to value type
            int i2 = (int)o;  // Boxing - explicit conversion

            // Unbox from dynamic
            dynamic d = i1;
            int i3 = (int)d;

            // Boxing, implicit, creates new copy
            MyPoint pt = new MyPoint(2.0, 3.0);
            IArea area = pt;

            // Unboxing, explicit,
            // from interface to value type,
            // creates new copy again
            MyPoint pt2 = (MyPoint)area;

            // Unbox to nullable type
            int? i4 = (int?)o;
            o = null;
            int? i5 = (int?)o;  // also works

            // Boxing, implicit, to ValueType
            // (creates copy)
            ValueType vty = pt;

            // Unboxing, creates copy
            MyPoint pt3 = (MyPoint)vty;
        }

#1,061 – Explicit Reference Conversions

An explicit reference conversion is a conversion between reference types that requires a cast operator.  Explicit reference conversions are not guaranteed to succeed and require a check at run-time to determine whether the conversion is allowed.  Because the check is done at run-time, the cast operator allows the conversion at compile-time.

Here are some examples:

        delegate void StringDel(string info);

        static void DelMethod(string info) { }

        static void Main(string[] args)
        {
            object o1 = new Dog("Bowser");
            dynamic dyn = o1;
            object o2 = new Cow("Bessie");
            Dog d2 = new BorderCollie("Shep");

            // From object or dynamic to ref type
            Dog d = (Dog)o1;
            d = dyn;

            // From base class to more derived type
            BorderCollie bc1 = (BorderCollie)d2;

            // From base class to interface that the
            // class does not implement, but that
            // the actual object does implement.
            // E.g. Dog does not implement IRun,
            //   but BorderCollie inherits from
            //   Dog and does implement IBark.
            //   d2 is of type Dog, but contains
            //   a BorderCollie.
            IRun ir1 = (IRun)d2;

            // From interface type to class that
            // implements the interface
            BorderCollie bc2 = (BorderCollie)ir1;

            // From one interface to the other, provided
            // that underlying object implements the
            // target interface.
            // E.g. BorderCollie implements both IRun
            //   and IFetch, ir1 is of type IRun but
            //   refers to a BorderCollie.  We can
            //   convert from variable of type IRun
            //   to one of type IFetch
            IFetch if1 = (IFetch)ir1;

            // From one array to another, provided that
            // explicit conversion exists between element
            // types.
            Dog[] dogs = { new Dog("Shep"), new Dog("Kirby") };
            object[] dogsAsObjs = dogs;
            Dog[] dogs2 = (Dog[])dogsAsObjs;

            // From System.Array to a specific array type
            Array arrints = new int[2];
            int[] ints = (int[])arrints;

            // From array to IList<>, if explicit conversion from
            // array element type to IList type exists.
            object[] objs = new Dog[2];
            IList<Dog> dogList = (IList<Dog>)objs;

            // From IList<> to array, if explicit conversion from
            // IList type to array element type exists
            Dog[] objs2 = new Dog[2];
            IList<object> dogList2 = objs2;  // implicit
            Dog[] dogs3 = (Dog[])dogList2;   // explicit

            // From Delegate to a specific delegate type
            StringDel myDel = DelMethod;
            Delegate genDel = myDel;  // implicit
            StringDel myDel2 = (StringDel)genDel;  // explicit
        }