11

Alright, I've been trying to find any information on this for a while. I built a small class to see how hard type-safe-enums are to implement for strings, because I want to use them for database field-names and such. I've never liked the fact that enums could only be used for integers.
However, even though I have implemented an implicit operator for this class, every time I try to use it, it gives me an invalid cast exception. I'm at a loss, as I can see nothing wrong with my code at this point.
Here's the class:

/// <summary>
/// SBool - type-safe-enum for boolean strings
/// </summary>
public sealed class SBool
{

    private readonly String name;
    private readonly int value;

    // these guys were for conversions. They might work better in another case,
    //  but for this one, they weren't very helpful.
    // ((I.e. they didn't work either.))
    //private static readonly Dictionary<SBool, String> stringsByBool = new Dictionary<SBool, String>();
    //private static readonly Dictionary<String, SBool> boolsByString = new Dictionary<String, SBool>();

    public static readonly SBool True = new SBool( 1, "true" );
    public static readonly SBool False = new SBool( 0, "false" );

    private SBool( int value, String name )
    {
        this.name = name;
        this.value = value;
        //stringsByBool[this] = name;
        //boolsByString[name] = this;
    }

    private SBool( SBool sbool )
    {
        this.name = sbool.name;
        this.value = sbool.value;
        //stringsByBool[this] = name;
        //boolsByString[name] = this;
    }

    public override String ToString()
    {
        return name;
    }

    /// <summary>
    /// allows implicit casting of SBools to strings
    /// </summary>
    /// <param name="sbool">the SBool to cast into a string</param>
    /// <returns>the string equivalent of the SBool (its value)</returns>
    public static implicit operator String( SBool sbool )
    {
        if ( sbool == SBool.True )
            return SBool.True.name;
        else
            return SBool.False.name;
    }

    /// <summary>
    /// implicitly cast a string into a SBool.
    /// </summary>
    /// <param name="str">the string to attempt to cast as a SBool</param>
    /// <returns>the SBool equivalent of the string,
    /// SBool.False if not either "true" or "false".</returns>
    public static explicit operator SBool( String str )
    {
        if ( !String.IsNullOrEmpty(str) && str.ToLower() == "true" )
            return SBool.True;
        else
            return SBool.False;
    }

    public static bool operator ==( SBool left, SBool right )
    {
        return left.value == right.value;
    }

    public static bool operator !=( SBool left, SBool right )
    {
        return left.value != right.value;
    }
}


This is failing on the check of a Session variable:
if( ( (string)Session["variable"] ) == SBool.False ) with an InvalidCastException,
and I quite frankly have no idea why.

Thanks in advance; cookies for anyone who can explain why this doesn't work (cookies not available in all areas). I'm going to get other things fixed, but let me know if there's anything that is unclear. For more info on Type-Safe enums, here's one of the SO posts I based this class off of.

[MetaEDIT] disregard this. I was horribly, horribly mistaken. [/edit]

Community
  • 1
  • 1
FireSBurnsmuP
  • 923
  • 10
  • 28
  • 1
    Have you tried casting `Session["variable"]` to an `SBool` instead of a string? Or to a `string` and then an `SBool`? – D Stanley Apr 02 '13 at 14:56
  • `sBool.False` is a Boolean correct...? if you are checking if it's equal why not cast that value on the right side of the `==` to `if( ( (string)Session["variable"] ) == SBool.False.ToString()` The `Casting error is correct btw` – MethodMan Apr 02 '13 at 14:58
  • 1
    Most probably exception is about this cast `(string)Session["variable"]` – Lanorkin Apr 02 '13 at 15:04
  • @Lanorkin: if I'm not mistaken `(string)` should not throw exceptions; if `Session["var"]` is invalid it will simply return "" - [citation](http://stackoverflow.com/questions/2099900/difference-between-tostring-and-as-string-in-c-sharp) @DJKRAZE: the point is to not have to ToString every time. that's why I'm using an implicit cast in the first place. – FireSBurnsmuP Apr 02 '13 at 15:52
  • 1
    @FireSBurnsmuP `Session` may contain object of absolutely any type, so you can easily get InvalidCastException. Just separate your expression in two lines and check which one throws Exception – Lanorkin Apr 02 '13 at 15:56
  • @Lanorkin: Alright, I see what my problem is: I forgot that the Session would be assigned as an SBool instead of implicitly casting to a string during assignment. I.e: `Session["var"] = SBool.True;` would not use the cast. Thanks for the illuminate; that explains the whole issue. – FireSBurnsmuP Apr 02 '13 at 16:08

3 Answers3

12

User defined implicit and explicit operators are entirely a compile time mechanism, not a runtime mechanism. Once the code is compiled the runtime has no idea about any user defined conversion operators.

When the compiler is doing it's type checking and sees that a Foo is expected but the actual value is a Bar it will first check through the built in language implicit conversion operators to see if an appropriate conversion exists. If it doesn't, it checks through the definition of both Foo and Bar for implicit conversion operators, if it finds one it will add in a call to the relevant static method to perform the conversion. Once you get to runtime only the built in language implicit operators will be applied.

In this case you aren't converting from SBool to a string, you're converting from object to string (as far as the compiler is concerned) and there is no conversion operator to handle that.

You need to first cast the result of the Session variable to SBool (which is what it really is) and then to some other type in order to be able to leverage the user defined conversion operators. So:

if( ( (SBool)Session["variable"] ) == SBool.False )

will work just fine.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • +1, if you implement an implicit conversion from `object` to `SBool` would it enable implicit unboxing? Probably not ... would be expensive for the compiler, undesired side effects etc. – Jodrell Apr 02 '13 at 15:21
  • @Jodrell It's a class, not a struct, so there is no boxing going on here. That said, there's no reason you couldn't have a conversion between the type and object. – Servy Apr 02 '13 at 15:37
  • If this is the case, then why would my exception specifically state that the invalid conversion is between `SBool` and `System.String`? If it thought it was an `object`, wouldn't it say that instead of `SBool` in the exception message? ((Sorry, the Message text should have been included in the original post.)) – FireSBurnsmuP Apr 02 '13 at 16:00
  • @FireSBurnsmuP For exactly the reasons that I've stated. At runtime the type is `SBool`, and you're trying to cast it to `String`. **There is no conversion operator from `SBool` to `String` at runtime**, and there is no way of making a user defined operator that would do so. You need to ensure that you are never in a position in which it attempt to find a conversion operator that does this *at runtime* by allowing the compiler to find the conversion operator *at compile time*. At compile time the result of the session value is `object`, but at runtime it's `SBool`. – Servy Apr 02 '13 at 16:03
  • @oh, and to your edit in which you say the session value is a string, the error message you just mentioned above indicates otherwise, so I suggest you check on that. – Servy Apr 02 '13 at 16:05
  • @Servy: I missed your statement about only built-in implicit operators existing at run-time, which explains a lot of my confusion. My edit was wrong. I now understand why your original answer is correct. As stated above, I thought the cast was failing on the right-side of `(string)Session["variable"] == SBool.False`, because I didn't realize that I hadn't casted my `SBool`s to `string`s when assigning to `Session`. Apparently I either need more sleep or my blood-sugar is too low right now. Either way, thanks. – FireSBurnsmuP Apr 02 '13 at 16:30
1

As Servy mentioned these types of cast happen at compile time. However there is a way to force this check to occur at runtime by using dynamic keyword. Instead of casting to SBool cast to dynamic. This will cause cast operators to be executed at run time.

In my opinion casting to SBool is a cleaner and less error prone solution.

Ivan
  • 1,735
  • 1
  • 17
  • 26
-1

Wen you're casting Session[] to a string, you're performing an explicit conversion. You don't have a SBool->String explicit operator defined, only implicit.

You need to add an explicit SBool->string conversion.

greggorob64
  • 2,487
  • 2
  • 27
  • 55