CLI VS C# and the meaning of cast.

What is a cast operation actually?
Well, that depends. Do you see the worlds from the C# language or from the CLI world.

This article is based on my experience using C# 3.5 SP1 and I have no idea if it applies to other .NET Language.

I was in a situation where an API call returned an int[] and I needed to use those values with an other API call but as a double[]. Looking at what immediate option the IntelliSense in Visual Studio showed me I found the Cast<t> and thought that this is just what I need for this job.

To my surprise, when running the code a runtime exception of System.InvalidCastException is thrown. My initial thought was why doesn't this work? The Cast<t> comes from the IEnumerable interface, thus if there exists an implicit conversion from one type to the other the cast should be possible. So why ins't it?

Looking at the specifications.
Reading the MSDN entry for IEnumerable.Cast<t> didn't give me any insight to why this didn't work

The C# Language Specification 7.6.6 defines the cast expression (T)E to be an explicit conversion(so this is why I think the name Cast<T> sucks).
Also 6.1.2 specifies a list if allowed implicit conversions.

Looking at ECMA-335 chap 8.3.3 could explain why they have made this particular implementation.
Unlike coercion, a cast never changes the actual type of
an object nor does it change the representation. Casting preserves the identity of objects.
Searching around on the Internet it does looks like it has worked as I would expect from the name (From C# definition) before 3.5 sp1
For instance this article.

Cast<t> for IEnumerable should really be Unbox<t> or something similar to make it visible that you can only perform this on objects that are in the same line of inheritance.

Ways around this technical implementation detail
To find out how to get the wanted result I tried to think of ways to implement the wanted behavior and then time the time it takes to see what is the best way. I tried to make a generic approach as to find any other limitations that the different implementations might have.

I found 5 approaches that could be feasible:
  • An implicit cast using the Array.CopyTo method.
  • The IEnumerable.Cast<T> (Just to make sure it wasn't just my specific case that made problems).
  • Use a LINQ query with Convert.ChangeType.
  • Use Array.ConvertAll with Convert.ChangeType.
  • Use Convert.ChangeType directly on the arrys.

That resulted in the following code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace TransformArrays
{
    class Program
    {
        private static void DoImplcitConversion<T1, T2>(T1[] fromArray, out T2[] toArray)
        {
            code = Code.ImplcitWithArray_CopyTo;
            Console.WriteLine("Implicit conversion from {0}[] to {1}[] using Array.CopyTo with {2} elemets", typeof(T1).Name, typeof(T2).Name, fromArray.Length);
            try
            {
                Stopwatch watch = StartNew();
                toArray = new T2[fromArray.Length];
                fromArray.CopyTo(toArray, 0);
                StopStopwatch(watch);
            }
            catch
            {
                toArray = null;
                Console.WriteLine(ExceptionMgs);
            }
        }

        private static void DoChangeType<T1, T2>(T1[] fromArray, out T2[] toArray)
        {
            code = Code.ChangeTypeOnArrays;
            Console.WriteLine("Convert.ChangeType from {0}[] to {1}[] using Array.CopyTo with {2} elemets", typeof(T1).Name, typeof(T2).Name, fromArray.Length);
            try
            {
                Stopwatch watch = StartNew();
                toArray = (T2[])System.Convert.ChangeType(fromArray, typeof(T2[]));
                StopStopwatch(watch);
            }
            catch
            {
                toArray = null;
                Console.WriteLine(ExceptionMgs);
            }
        }

        private static void DoCast<T1, T2>(T1[] fromArray, out T2[] toArray)
        {
            code = Code.Cast;
            Console.WriteLine("Using IEnumerable.Cast<> to change from {0}[] to {1}[] with {2} elemets", typeof(T1).Name, typeof(T2).Name, fromArray.Length);
            try
            {
                Stopwatch watch = StartNew();
                toArray = fromArray.Cast<t2>().ToArray();
                StopStopwatch(watch);
            }
            catch
            {
                toArray = null;
                Console.WriteLine(ExceptionMgs);
            }
        }

        private static void DoConversionWithLambdaAndLinq<T1, T2>(T1[] fromArray, out T2[] toArray)
        {
            code = Code.Linq_SelectWithChangeType;
            Console.WriteLine("Using IEnumerable<>.Selectx => ({1})Convert.ChangeType(x, typeof({1}))).ToArray() to change from {0}[] to {1}[] with {2} elemets", typeof(T1).Name, typeof(T2).Name, fromArray.Length);
            try
            {
                Stopwatch watch = StartNew();
                toArray = fromArray.Select(x => (T2)Convert.ChangeType(x, typeof(T2))).ToArray();
                StopStopwatch(watch);
            }
            catch
            {
                toArray = null;
                Console.WriteLine(ExceptionMgs);
            }
        }
        
        private static void DoConversionWithLambdaAndArrayConvert<T1, T2>(T1[] fromArray, out T2[] toArray)
        {
            code = Code.Array_ConvertAllWithChangeType;
            Console.WriteLine("Using Array.ConvertAll<>(Input, x => ({1})Convert.ChangeType(x, typeof({1}))) to change from {0}[] to {1}[] with {2} elemets", typeof(T1).Name, typeof(T2).Name, fromArray.Length);
            try
            {
                Stopwatch watch = StartNew();
                toArray = Array.ConvertAll(fromArray, x => (T2)Convert.ChangeType(x, typeof(T2)));
                StopStopwatch(watch);
            }
            catch
            {
                toArray = null;
                Console.WriteLine(ExceptionMgs);
            }
        }

         /******************************************************************************
         *                                                                             *
         *                Code below is for running the examplas and                   *
         *                        collecting data code only.                           *
         *                                                                             *
         ******************************************************************************/

        enum Code { ImplcitWithArray_CopyTo, Cast, Linq_SelectWithChangeType, Array_ConvertAllWithChangeType, ChangeTypeOnArrays };
        enum FromTo { IntDouble, DoubleInt }

        static int Iterations = 25;
        static Code code = Code.ImplcitWithArray_CopyTo;
        static FromTo fromTo = FromTo.IntDouble;
        static string TimerMgs = "Execution took {0}" + Environment.NewLine;
        static string ExceptionMgs = "Exception thrown" + Environment.NewLine;
        static Dictionary<FromTo, Dictionary<Code, TimeSpan>> timeCollection = new Dictionary<FromTo, Dictionary<Code, TimeSpan>>();
        static double[] doubles = Enumerable.Range(1, 10000000).Select(x => (double)x).ToArray();
        static int[] ints = Enumerable.Range(1, 10000000).ToArray();

        static void Main(string[] args)
        {
            for (int i = 0; i < Iterations; i++)
            {
                Console.WriteLine("round {0}", i + 1);
                FromIntToDouble();
                Console.WriteLine();
                FromDoubleToInt();
            }

            foreach (var ft in timeCollection.Keys)
            {
                foreach (var c in timeCollection[ft].Keys)
                {
                    Console.WriteLine("{0}, {1} : {2}", ft.ToString(), c.ToString().PadRight(8, ' '), timeCollection[ft][c].TotalMilliseconds / Iterations);
                }
            }
        }

        private static void FromDoubleToInt()
        {
            fromTo = FromTo.DoubleInt;
            int[] ints = null;
            DoImplcitConversion<double, int>(doubles, out ints);
            DoCast<double, int>(doubles, out ints);
            DoConversionWithLambdaAndLinq<double, int>(doubles, out ints);
            DoConversionWithLambdaAndArrayConvert<double, int>(doubles, out ints);
            DoChangeType<double, int>(doubles, out ints);
        }

        private static void FromIntToDouble()
        {
            fromTo = FromTo.IntDouble;
            double[] doubles = null;
            DoImplcitConversion<int, double>(ints, out doubles);
            DoCast<int, double>(ints, out doubles);
            DoConversionWithLambdaAndLinq<int, double>(ints, out doubles);
            DoConversionWithLambdaAndArrayConvert<int, double>(ints, out doubles);
            DoChangeType<int, double>(ints, out doubles);
        }

        public static Stopwatch StartNew()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            return sw;
        }

        public static void StopStopwatch(Stopwatch sw)
        {
            sw.Stop();
            Console.WriteLine(TimerMgs, sw.Elapsed);
            if (!timeCollection.ContainsKey(fromTo))
            {
                timeCollection.Add(fromTo, new Dictionary<Code, TimeSpan>());
            }

            if (!timeCollection[fromTo].ContainsKey(code))
            {
                timeCollection[fromTo].Add(code, new TimeSpan());
            }
            timeCollection[fromTo][code] += sw.Elapsed;
        }
    }
}

Benchmark results
I have tried making some benchmark of the different way of converting by making large arrays of 10.000.000 item and convert the data into another type 25 times.

This is the results
|-------------------------------------------|
|              |  double->int | int->double |
|--------------+--------------+-------------|
| Implcit      |       N/A    |      145 ms |
|--------------+--------------+-------------|
| ConvertAll   |      2015 ms |     1659 ms |
|--------------+--------------+-------------|
| Select       |      3003 ms |     2568 ms |
|--------------+--------------+-------------|
| Cast>T<      |       N/A    |      N/A    |
|--------------+--------------+-------------|
| Array Change |       N/A    |      N/A    |
|-------------------------------------------|

Comments

  1. The major intent of sports betting is to win additional cash. With the exception of unfold betting, ‘draw no bet’ wagers and a few other examples, a wager may have two attainable outcomes. Either you win a revenue primarily based on the bookmaker odds, or you lose your wager. Inefficient markets enable investors to consistently outperform the market. To reveal that inefficiencies exist in sports https://casino.edu.kg/%EC%86%8C%EC%9A%B8%EC%B9%B4%EC%A7%80%EB%85%B8.html activities betting markets, we created a betting algorithm that generates above market returns for the NFL, NBA, NCAAF, NCAAB, and WNBA betting markets.

    ReplyDelete
  2. The Welcome Bonus at Jackpot City has been around for a very very long time} now and since it’s such a generous supply, it’s a player’s favourite. It’s a100% match bonus spread over 4 deposits with a maximum of $/€400 available per deposit. To discover out more about method 토토사이트 to|tips on how to} greatest use your Welcome Bonus and casino promotions, take a look at|check out} our How-To information. When you resolve to change from regular slots to Live Dealer casino gaming, JackpotCity has everything you need for the thrills of the actual deal, in real time, with real croupiers.

    ReplyDelete

Post a Comment