Is there a workaround for generic type constraint of "special class" Enum in C# 3.0? [duplicate]
Asked Answered
P

4

11

Update: See the bottom of this question for a C# workaround.

Hi there,

Consider the following extension method:

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}

This will, as you may know, throw an error at compile-time, since a class is not normally allowed to inherit from System.Enum. The problem is that any enumeration specified using the enum keyword does in fact inherit from System.Enum, so the above code would be the ideal way to limit an extension method to enumerations only.

Now the obvious work-around here is to use Enum instead of T, but then you lose the benefits of generic types:

MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);

The above code would throw a compile-time error using generic types, while it can only throw a runtime error using the Enum type (if I implement it to do so.)

Are there any compiler options that can be used to turn off the constraint check, or is there some other nifty way to do this?

Before it is suggested, I would like to say that I will not be using where T : struct or some such, since then you'd be able to do weird stuff like 123.HasFlags(456).

I'm stumped as to why this error exists at all... It's the same problem you'd get using where T : System.Object, but for that you have where T : class... Why is there no where T : enum?

C# workaround

Jon Skeet has started work on a library that compiles classes with a constraint to an IEnumConstraint, which is then replaced with System.Enum post-build. This is, I believe, the closest one can get to working around this issue at this time.

See:

If this workaround is unfeasible, you will have to write your library as C++/CLI code, which does not limit what can be used for generic type constraints (see the code in my answer below.)

Philoprogenitive answered 10/9, 2009 at 8:31 Comment(8)
Maybe didn't feel such a constraint was needed in all but very rare cases. Who knows... I would agree with them if that was the case :)Unanswerable
@Skurmedel: What's odd is that it's specifically prohibited... in other words the spec has been made more complicated to restrict it. I don't see why. Maybe Eric Lippert will explain :)Zebada
Yes I agree with Jon Skeet, although it could just be that it was overlooked (they used the same inheritance check for constraints as they do for class inheritance)... But I'm really not familiar with the inner workings of C#.Philoprogenitive
Eric Lippert previously posted about Enum Type Constraints in C#: https://mcmap.net/q/22283/-enum-type-constraints-in-c-duplicate/…Roderich
I see. Can I take it from his answer that even if there had been an option to turn off the compiler error, the code would still not work? Unless that is the case, I don't see why this limitation is forced on me (it would be nice to be able to explicitly turn off the CS0702 error.)Philoprogenitive
I wrote a C++/CLI library for the functionality I wanted and it worked fine. This leads me to believe that it should be possible to do in C# too... But I guess we won't see this until C# 5.0 even if the designers can be convinced to allow it? (I'd still vote for a compiler switch to turn off the CS0702 error though... But maybe that's not possible.)Philoprogenitive
No reason to vote to close this... I specifically mentioned all the solutions provided to your question here, so that only other solutions would be provided, and now we've got Jon's library on the way as well as the C++/CLI solution. Your question was one year old and I was looking for none of the answers it had.Philoprogenitive
Fair enough - if you hadn't re-asked this we probably wouldn't have got the recent activity. It is basically the same question though. So +1 and a vote to close as dupe ;PBettis
Z
10

EDIT: A library is now available supporting this via ildasm/ilasm: UnconstrainedMelody.


Members of the C# team have previously said they'd like to be able to support where T : Enum and where T : Delegate, but that it's never been a high enough priority. (I'm not sure what the reasoning is for having the restriction in the first place, admittedly...)

The most practical workaround in C# is:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    // ...
}

That loses compile-time checking for the "enum-ness" but keeps the check that you're using the same type in both places. It has the execution-time penalty of the check as well, of course. You can avoid that execution-time penalty after the first call by using a generic nested type for the implementation which throws the exception in a static constructor:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    return EnumHelper<T>.HasFlags(value, flags);
}

private class EnumHelper<T> where T : struct
{
    static EnumHelper()
    {
        if (!typeof(Enum).IsAssignableFrom(typeof(T))
        {
            throw new InvalidOperationException(); // Or something similar
        }
    }

    internal static HasFlags(T value, T flags)
    {
        ...
    }
}

As Greco mentions, you could write the method in C++/CLI and then reference the class library from C# as another option.

Zebada answered 10/9, 2009 at 8:38 Comment(13)
I think it's important to keep extension methods limited in scope and therefore I will not be using where T : struct due to the reasons mentioned in my question (123.HasFlags(456) would compile fine.) I do like Greco's solution, but I can't justify creating a whole library for one method. Is it possible to set up a process to "inject" the C++/CLI into a C# library when compiling?Philoprogenitive
@Blixt: Well, you could look into binary rewriting, but it's probably going to be ugly. I wonder whether there'd be interest in an open source project for a bunch of "useful things one can do with delegates and enums"...Zebada
Okay, I would only do it if there was an "easy" way to do it =) And yes, judging from the discussions I've found on Google, this problem isn't all that rare, and I would definitely appreciate such a library.Philoprogenitive
Okay, will consider it. I'd have to find competent C++/CLI developers though... my C++ is awful :) Another alternative might be to write the code in C# with special markers, and do binary rewriting on the library afterwards to change the constraints. Hmm.Zebada
Nice =) Yeah, once upon a time I was starting to get the hang of C++, then along came languages like Python and C# and spoiled me!Philoprogenitive
Have a look at my answer to this question for C++/CLI code that implements the HasFlags functionality I was looking for.Philoprogenitive
You could use ILMerge (research.microsoft.com/en-us/people/mbarnett/ilmerge.aspx) to include what would be a fairly vestigial C++/CLI assemly into your main code.Hernando
Interesting! Thanks for the link =)Philoprogenitive
I'm accepting this answer as it leads to the only viable C# solution I've seen. Greco's answer is still a very nice solution, though.Philoprogenitive
@Blixy: Have edited the library link into the answer...Zebada
Don't forget you want : enum, not : Enum, to distinguish whether to allow passing System.Enum itself or not.Dempster
Jon Skeet's MSDN blog has changed from jon_skeet to jonskeet.. so the correct url for the blog post is now: blogs.msmvps.com/jonskeet/2009/09/10/…Kinna
@Yngvar: Well, it's now codeblog.jonskeet.uk, so: codeblog.jonskeet.uk/2009/09/10/…Zebada
B
3

Actually, it is possible, with an ugly trick. However, it cannot be used for extension methods.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.Parse<DateTimeKind>("Local")

If you want to, you can give Enums<Temp> a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

Bodnar answered 13/9, 2009 at 11:26 Comment(1)
FYI A VB.NET version of this excellent, if ugly, trick.Ayer
P
2

I couldn't resist having a go at the C++ work-around, and since I got it to work I figured I'd share it with the rest of ya!

Here's the C++ code (my C++ is very rusty so please point out any errors, in particular how the arguments are defined):

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace Blixt
{
namespace Utilities
{
    [Extension]
    public ref class EnumUtility abstract sealed
    {
    public:
        generic <typename T> where T : value class, Enum
        [Extension]
        static bool HasFlags(T value, T flags)
        {
            __int64 mask = Convert::ToInt64(flags);
            return (Convert::ToInt64(value) & mask) == mask;
        }
    };
}
}

And the C# code for testing (console application):

using System;
using Blixt.Utilities;

namespace Blixt.Playground
{
    [Flags]
    public enum Colors : byte
    {
        Black = 0,
        Red = 1,
        Green = 2,
        Blue = 4
    }

    [Flags]
    public enum Tastes : byte
    {
        Nothing = 0,
        Sour = 1,
        Sweet = 2,
        Bitter = 4,
        Salty = 8
    }

    class Program
    {
        static void Main(string[] args)
        {
            Colors c = Colors.Blue | Colors.Red;
            Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
            Console.WriteLine("Blue?           {0}", c.HasFlags(Colors.Blue));
            Console.WriteLine("Green?          {0}", c.HasFlags(Colors.Green));
            Console.WriteLine("Red and blue?   {0}", c.HasFlags(Colors.Red | Colors.Blue));

            // Compilation error:
            //Console.WriteLine("Sour?           {0}", c.HasFlags(Tastes.Sour));

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}
Philoprogenitive answered 10/9, 2009 at 10:25 Comment(0)
D
1

You can achieve this using IL Weaving and ExtraConstraints

Allows you to write this code

public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
    // ...
} 

What gets compiled

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
} 
Dias answered 20/7, 2012 at 7:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.