ChatGPT解决这个技术问题 Extra ChatGPT

Casting vs using the 'as' keyword in the CLR

When programming interfaces, I've found I'm doing a lot of casting or object type conversion.

Is there a difference between these two methods of conversion? If so, is there a cost difference or how does this affect my program?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Also, what is "in general" the preferred method?

Could you add a small example of why you're using the casts in the first place to the question, or perhaps start a new one? I'm kinda interested in why you would need the cast for unit testing only. I think it's outside of the scope of this question though.
I can probably change my unit test to prevent this need for this. Basically it boils down to the fact that I have a property on my concrete object that isn't in the interface. I need to set that property but real life that property would have been set by other means. Does that answer your question?
As Patrik Hägne astutely points out below, there IS a difference.

J
Jon Skeet

The answer below the line was written in 2008.

C# 7 introduced pattern matching, which has largely replaced the as operator, as you can now write:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Note that tt is still in scope after this, but not definitely assigned. (It is definitely assigned within the if body.) That's slightly annoying in some cases, so if you really care about introducing the smallest number of variables possible in every scope, you might still want to use is followed by a cast.

I don't think any of the answers so far (at the time of starting this answer!) have really explained where it's worth using which.

Don't do this: // Bad code - checks type twice for no reason if (randomObject is TargetType) { TargetType foo = (TargetType) randomObject; // Do something with foo } Not only is this checking twice, but it may be checking different things, if randomObject is a field rather than a local variable. It's possible for the "if" to pass but then the cast to fail, if another thread changes the value of randomObject between the two.

If randomObject really should be an instance of TargetType, i.e. if it's not, that means there's a bug, then casting is the right solution. That throws an exception immediately, which means that no more work is done under incorrect assumptions, and the exception correctly shows the type of bug. // This will throw an exception if randomObject is non-null and // refers to an object of an incompatible type. The cast is // the best code if that's the behaviour you want. TargetType convertedRandomObject = (TargetType) randomObject;

If randomObject might be an instance of TargetType and TargetType is a reference type, then use code like this: TargetType convertedRandomObject = randomObject as TargetType; if (convertedRandomObject != null) { // Do stuff with convertedRandomObject }

If randomObject might be an instance of TargetType and TargetType is a value type, then we can't use as with TargetType itself, but we can use a nullable type: TargetType? convertedRandomObject = randomObject as TargetType?; if (convertedRandomObject != null) { // Do stuff with convertedRandomObject.Value } (Note: currently this is actually slower than is + cast. I think it's more elegant and consistent, but there we go.)

If you really don't need the converted value, but you just need to know whether it is an instance of TargetType, then the is operator is your friend. In this case it doesn't matter whether TargetType is a reference type or a value type.

There may be other cases involving generics where is is useful (because you may not know whether T is a reference type or not, so you can't use as) but they're relatively obscure.

I've almost certainly used is for the value type case before now, not having thought of using a nullable type and as together :)

EDIT: Note that none of the above talks about performance, other than the value type case, where I've noted that unboxing to a nullable value type is actually slower - but consistent.

As per naasking's answer, is-and-cast or is-and-as are both as fast as as-and-null-check with modern JITs, as shown by the code below:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

On my laptop, these all execute in about 60ms. Two things to note:

There's no significant difference between them. (In fact, there are situations in which the as-plus-null-check definitely is slower. The above code actually makes the type check easy because it's for a sealed class; if you're checking for an interface, the balance tips slightly in favour of as-plus-null-check.)

They're all insanely fast. This simply will not be the bottleneck in your code unless you really aren't going to do anything with the values afterwards.

So let's not worry about the performance. Let's worry about correctness and consistency.

I maintain that is-and-cast (or is-and-as) are both unsafe when dealing with variables, as the type of the value it refers to may change due to another thread between the test and the cast. That would be a pretty rare situation - but I'd rather have a convention which I can use consistently.

I also maintain that the as-then-null-check gives a better separation of concerns. We have one statement which attempts a conversion, and then one statement which uses the result. The is-and-cast or is-and-as performs a test and then another attempt to convert the value.

To put it another way, would anyone ever write:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

That's sort of what is-and-cast is doing - although obviously in a rather cheaper way.


Here is the cost of is/as/casting in terms of IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
In case, if targetObject might be target type, why using of "is" and cast combination considered a bad practice? I mean,it generates a slower code, but in this case intentions are more clear than AS cast, like "Do something if targetObject is targetType", instead of "Do something if targetObject is null", Moreover AS clause will create a unnecessary variable out of IF scope.
@Valera: Good points, although I'd suggest that the as/null test is sufficiently idiomatic that the intention should be clear to almost all C# developers. I don't like the duplication involved in the is + cast, personally. I'd actually like a sort of "as-if" construct which does both actions in one. They go together so often...
@Jon Skeet:sorry for my late.Is And Cast:2135, Is And As:2145, As And null check: 1961,specs: OS:Windows Seven, CPU:i5-520M, 4GB of DDR3 1033 ram, benchmark on array of 128,000,000 items.
With C# 7 you can do: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value} or use switch/case see docs
F
Frank V

"as" will return NULL if not possible to cast.

casting before will raise an exception.

For the performance, raising an exception is usually more costly in time.


Exception raising is more costly, but if you know that the object can be cast correctly, as requires more time because of the safety check (see Anton's response). However, the cost of the safety check is, I believe, quite small.
The cost of potentially raising an exception is a factor to consider, but it is often the correct design.
@panesofglass - For reference types, the conversion compatibility will always be checked at run-time for both as and cast, so that factor will not distinguish between the two options. (If this were not so, then cast could not raise an exception.)
@Frank - If you are required to use a pre-generics collection, for example, and a method in your API requires a list of Employees, and some joker instead passes a list of Products, then an invalid cast exception may be appropriate to signal the violation of the interface requirements.
@user29439 sorry, but IL OP code "isinst" for "as" operator is faster than OP code "castclass" used for direct cast. So, for reference types "as" will perform faster even if object can be casted with no exception. Unity engine generates more performant code for "as" with IL2CPP as well.
C
Chris S

Here's another answer, with some IL comparison. Consider the class:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Now look at the IL each method produces. Even if the op codes mean nothing to you, you can see one major difference - isinst is being called followed by castclass in the DirectCast method. So two calls instead of one basically.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

The isinst keyword versus the castclass

This blog post has a decent comparison between the two ways of doing it. His summary is:

In a direct comparison, isinst is quicker than castclass (although only slightly)

When having to perform checks to ensure the conversion was successful, isinst was significantly quicker than castclass

A combination of isinst and castclass should not be used as this was far slower than the quickest "safe" conversion (over 12% slower)

I personally always use As, because it's easy to read and is recommended by the .NET development team (or Jeffrey Richter anyway)


I was looking for clear explanation for casting vs as , this answer makes it way clearer as it involves common intermediate language step by step explanation. Thanks!
P
Patrik Hägne

One of the more subtle differences between the two is that the "as" keyword can not be used for casting when a cast operator is involved:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

This will not compile (although I think it did in previous versions) on the last line since the "as" keywords do not take cast operators into account. The line string cast = (string)f; works just fine though.


A
Anton Gogolev

as never throws an exception if it cannot perform the conversion returning null instead (as operates on reference types only). So using as is basically equivalent to

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

C-style casts, on the other hand, throw an exception when no conversion is possible.


Equivalent, yes, but not the same. This generates way more code then as.
t
toad

Not really an answer to your question, but what I think is an important related point.

If you are programming to an interface you shouldn't be needing to cast. Hopefully these casts are very rare. If not you likely need to rethink some of your interfaces.


The casting, so far, has mostly been needed for my Unit Testing but thank you for bringing it up. I'll keep that in my mind while I work on this.
Agreed with toad, I'm also curious why the unit testing aspect is relevant to the casting for you @Frank V. Where there is a need for casting, there is often a need for redesign or refactor as it suggests that you're trying to shoehorn different problems where they should be managed differently.
@TheSenator This question is well over 3 years old so I don't really remember. But I was probably aggressively using the interfaces even when unit testing. Possibly because I was using the factory pattern and didn't have access to a public constructor on the target objects to test.
n
naasking

Please ignore Jon Skeet's advice, re:avoid test-and-cast pattern, ie.:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

The idea that this costs more than a cast and a null test is a MYTH:

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

It's a micro-optimization that does not work. I ran some real tests, and test-and-cast is actually faster than cast-and-null-comparison, and it's safer too because you don't have the possibility of having a null reference in the scope outside the if should the cast fail.

If you want a reason why test-and-cast is faster, or at least not slower, there's a simple and complex reason.

Simple: even naive compilers will coalesce two similar operations, like test-and-cast, into a single test and branch. cast-and-null-test may force two tests and a branch, one for the type test and conversion to null on failure, one for the null check itself. At the very least, they will both optimize to a single test and branch, so test-and-cast would be neither slower nor faster than cast-and-null-test.

Complex: why test-and cast is faster: cast-and-null-test introduces another variable into the outer scope which the compiler must track for liveness, and it may not be able to optimize away that variable depending on how complex your control-flow is. Conversely, test-and-cast introduces a new variable only in a delimited scope so the compiler knows that the variable is dead after the scope exits, and so can optimize register allocation better.

So please, PLEASE let this "cast-and-null-test is better than test-and-cast" advice DIE. PLEASE. test-and-cast is both safer and faster.


@naasking: If you test twice (as per your first snippet), there's a chance that the type will change between the two tests, if it's a field or ref parameter. It's safe for local variables, but not for fields. I would be interested to run your benchmarks, but the code you've given in your blog post is not complete. I agree with not micro-optimising, but I don't think using the value twice is any more readable or elegant than using "as" and a nullity test. (I would definitely use a straight cast rather than "as" after an is, btw.)
I also don't see why it's any safer. I've shown why it's less safe, in fact. Sure, you end up with a variable in scope which may be null, but unless you start using that outside the scope of the subsequent "if" block, you're fine. The safety concern I've raised (around fields changing their value) is a genuine concern with the code shown - your safety concern requires developers to be lax in other code.
+1 for pointing out that is/cast or as/cast is no slower in reality, mind you. Having run a complete test myself, I can confirm it makes no difference as far as I can see - and frankly you can run a boggling number of casts in a very small time. Will update my answer with full code.
Indeed, if the binding is not a local there is a chance of a TOCTTOU bug (time-of-check-to-time-of-use), so good point there. As for why it's safer, I work with a lot of junior developers that like to reuse locals for some reason. cast-and-null is thus very real hazard in my experience, and I've never run into a TOCTTOU situation since I don't design my code that way. As for runtime test speed, it's even faster than virtual dispatch [1]! Re: code, I'll see if I can find the source for the cast test. [1] higherlogics.blogspot.com/2008/10/…
@naasking: I've never run into the local reuse problem - but I'd say it's easier to spot in code review than the more subtle TOCTTOU bug. It's also worth pointing out that I've just rerun my own benchmark checking for interfaces instead of a sealed class, and that tips the performance in favour of as-then-null-check... but as I've said, performance isn't why I'd choose any particular approach here.
T
TheSmurf

If the cast fails, the 'as' keyword doesn't throw an exception; it sets the variable to null (or to its default value for value types) instead.


No default values for value types. As can not be used for casting value types.
The 'as' keyword doesn't work on value types actually, so it always sets to null.
f
f3lix

This is not an answer to the question but comment to the question's code example:

Usually you should not have to cast an Object from e.g. IMyInterface to MyClass. The great thing about interfaces is that if you take an object as input that implements an interface, than you don't have to care what kind of object you are getting.

If you cast IMyInterface to MyClass, than you already assume that you get an object of type MyClass and it makes no sense to use IMyInterface, because if you feed your code with other classes that implement IMyInterface, it would break your code...

Now, my advice: if your interfaces are well designed you can avoid a lot of typecasting.


J
Jeffrey L Whitledge

The as operator can only be used on reference types, it cannot be overloaded, and it will return null if the operation fails. It will never throw an exception.

Casting can be used on any compatible types, it can be overloaded, and it will throw an exception if the operation fails.

The choice of which to use depends on the circumstances. Primarily, it's a matter of whether you want to throw an exception on a failed conversion.


'as' can also be used on nullable value types, which provides an interesting pattern. See my answer for code.
C
CoperNick

My answer is only about speed in cases when we don't check the type and we don't check nulls after casting. I added two additional tests to Jon Skeet's code:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Result:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Don't try to focus on speed (as I did) because all this is very very fast.


Similarly, in my testing, I found that as conversion (without the error checking) ran about 1-3% faster than casting (around 540ms versus 550ms on 100 million iterations). Neither will make or break your application.
O
Olivier MATROT

If you use the Office PIAs targeting the .NET Framework 4.X you should use the as keyword, otherwise it won't compile.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Casting is OK when targeting .NET 2.0 though:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

When targeting .NET 4.X the errors are:

error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'


O
Oleg

What you choose strongly depends on what required. I prefer explicit casting

IMyInterface = (IMyInterface)someobj;

because if object should by of IMyInterface type and it is not - it is definitely problem. It is better to get error as early as possible because exact error will be fixed instead of fixing its side effect.

But if you deal with methods that accepts object as parameter then you need to check its exact type prior executing any code. In such case as would be useful so you can avoid InvalidCastException.


V
Veverke

Besides all what was already exposed here, I just came across a practical difference I think is worth noting, between explicit casting

var x = (T) ...

versus using the as operator.

Here is the example:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Bottom line: GenericCaster2 will not work with struct types. GenericCaster will.


C
Cerebrus

The as keyword works the same as an explicit cast between compatible reference types with the major difference that it does not raise an exception if conversion fails. Rather, it yields a null value in the target variable. Since Exceptions are very expensive in terms of performance, it is considered a much better method of casting.


Not the same, as one calls CastClass and the other calls IsInst in IL code.
D
Darryl Braaten

It depends, do you want to check for null after using "as" or would you prefer your app to throw an exception?

My rule of thumb is if I always expect the variable to be of the type I am expecting at the time I want I use a cast. If it is possible that the variable will not cast to what I want and I am prepared to handle nulls from using as, I will use as.


j
juFo

Have a look at these links:

http://gen5.info/q/2008/06/13/prefix-casting-versus-as-casting-in-c/

http://www.codeproject.com/Articles/8052/Type-casting-impact-over-execution-performance-in

they show you some details and performance tests.


T
Tobias Knauss

The OP's problem is limited to a specific casting situation. The title covers much more situations. Here's an overview of all relevant casting situations that I currently can think of:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}