Esiste una soluzione alternativa per il vincolo di tipo generico di & # 8220; classe speciale & # 8221; Enum in C # 3.0? [duplicare]

StackOverflow https://stackoverflow.com/questions/1404077

Domanda

    

Questa domanda ha già una risposta qui:

         
  

Aggiornamento: vedi il fondo di questa domanda per una soluzione alternativa a C #.

Ciao,

Considera il seguente metodo di estensione:

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

Questo, come saprai, genererà un errore in fase di compilazione, dal momento che una classe normalmente non può ereditare da System.Enum . Il problema è che qualsiasi enumerazione specificata usando la parola chiave enum eredita effettivamente da System.Enum , quindi il codice sopra sarebbe il modo ideale per limitare un metodo di estensione alle enumerazioni solo.

Ora la soluzione ovvia qui è usare Enum invece di T , ma poi perdi i vantaggi dei tipi generici:

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

Il codice sopra riportato genererebbe un errore in fase di compilazione utilizzando tipi generici, mentre può solo generare un errore di runtime utilizzando il tipo Enum (se lo implemento per farlo.)

Esistono opzioni di compilatore che possono essere utilizzate per disattivare il controllo dei vincoli o c'è qualche altro modo ingegnoso per farlo?

Prima che venga suggerito, vorrei dire che non userò dove T: struct o qualcosa del genere, da allora potresti essere in grado di fare cose strane come 123 .HasFlags (456) .

Sono sconcertato sul motivo per cui questo errore esiste affatto ... È lo stesso problema che otterresti usando dove T: System.Object , ma per questo hai dove T: class ... Perché non c'è dove T: enum ?

  

Soluzione alternativa C #

     

Jon Skeet ha iniziato a lavorare su una libreria che compila le classi con un vincolo a un IEnumConstraint , che viene poi sostituito con System.Enum post-build. Questo è, credo, il più vicino in grado di aggirare questo problema in questo momento.

     

See:

           

Se questa soluzione alternativa non è possibile, dovrai scrivere la tua libreria come codice C ++ / CLI, che non limita ciò che può essere utilizzato per vincoli di tipo generico (vedi il codice nella mia risposta di seguito.)

È stato utile?

Soluzione

EDIT: è ora disponibile una libreria che supporta questo tramite ildasm / ilasm: UnconstrainedMelody .


I membri del team di C # avevano precedentemente affermato di voler gradire per poter supportare dove T: Enum e dove T: Delegate , ma che non è mai stata una priorità abbastanza alta. (Non sono sicuro di quale sia il ragionamento per avere la limitazione in primo luogo, devo ammettere ...)

La soluzione più pratica in C # è:

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

Ciò perde la verifica del tempo di compilazione per "enum-ness" ma controlla che stai utilizzando lo stesso tipo in entrambi i posti. Ovviamente ha anche la penalità del tempo di esecuzione dell'assegno. Puoi evitare la penalità del tempo di esecuzione dopo la prima chiamata utilizzando un tipo nidificato generico per l'implementazione che genera l'eccezione in un costruttore statico:

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)
    {
        ...
    }
}

Come menziona Greco, è possibile scrivere il metodo in C ++ / CLI e quindi fare riferimento alla libreria di classi da C # come un'altra opzione.

Altri suggerimenti

In realtà, è possibile, con un brutto trucco. Tuttavia, non può essere utilizzato per i metodi di estensione.

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")

Se vuoi, puoi dare a Enums < Temp > un costruttore privato e una classe ereditata astratta nidificata pubblica con Temp come Enum , per impedire le versioni ereditate per i non-enum.

Non ho resistito al tentativo di aggirare il C ++, e dato che l'ho fatto funzionare ho pensato di condividerlo con il resto di te!

Ecco il codice C ++ (il mio C ++ è molto arrugginito, quindi per favore segnala eventuali errori, in particolare come sono definiti gli argomenti):

#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;
        }
    };
}
}

E il codice C # per i test (applicazione console):

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);
        }
    }
}

Puoi farlo utilizzando IL Weaving e ExtraConstraints

Ti permette di scrivere questo codice

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

Cosa viene compilato

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
} 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top