Dlaczego C # zabrania generycznych typów atrybutów?

510

Powoduje to wyjątek czasu kompilacji:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Wiem, że C # nie obsługuje atrybutów ogólnych. Jednak po długim Googlingu nie mogę znaleźć powodu.

Czy ktoś wie, dlaczego typy ogólne nie mogą pochodzić Attribute? Jakieś teorie?

Bryan Watts
źródło
17
Mógłbyś zrobić [Validates (typeof (string)] - Zgadzam się, że generyczne byłyby ładniejsze ...
ConsultUtah
20
Mimo że jest to bardzo późne uzupełnienie tego pytania, to smutne, że nie tylko same atrybuty, ale także abstrakcyjne klasy atrybutów (które oczywiście nie mogą być tworzone jako atrybuty) nie zostały uwzględnione, w ten sposób: abstract class Base<T>: Attribute {}które można wykorzystać do tworzenia innych niż ogólne klasy pochodne, takie jak:class Concrete: Base<MyType> {}
Lucero
88
Pragnę ogólnych atrybutów i atrybutów akceptujących lambdas. Wyobraź sobie coś takiego [DependsOnProperty<Foo>(f => f.Bar)]lub [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń,
3
Byłoby to niezwykle przydatne w sytuacji, którą właśnie spotkałem; dobrze byłoby utworzyć LinkedValueAttribute, który zaakceptowałby typ ogólny i wymusił ten typ na podanej rzeczywistej wartości. Mógłbym użyć go do wyliczeń, aby określić „domyślną” wartość innego wyliczenia, które powinno być użyte, jeśli ta wartość wyliczenia zostanie wybrana. Można określić wiele z tych atrybutów dla różnych typów i mogę uzyskać potrzebną wartość na podstawie potrzebnego typu. Mogę ustawić go tak, aby używał Typu i Obiektu, ale mocne wpisanie byłoby dużym plusem.
KeithS,
10
Jeśli nie przeszkadza ci mała IL, wygląda to obiecująco .
Jarrod Dixon

Odpowiedzi:

358

Cóż, nie mogę odpowiedzieć, dlaczego nie jest dostępny, ale może potwierdzić, że to nie jest kwestia CLI. Specyfikacja CLI nie wspomina o tym (o ile widzę) i jeśli używasz IL bezpośrednio, możesz utworzyć atrybut ogólny. Część specyfikacji C # 3, która ją zakazuje - sekcja 10.1.4 „Podstawowa specyfikacja klasy” nie daje żadnego uzasadnienia.

Adnotacja specyfikacji ECMA C # 2 również nie zawiera żadnych pomocnych informacji, chociaż stanowi przykład tego, co jest niedozwolone.

Moja kopia specyfikacji C # 3 z adnotacjami powinna przyjechać jutro ... Zobaczę, czy to da więcej informacji. W każdym razie zdecydowanie jest to decyzja językowa, a nie decyzja w czasie wykonywania.

EDYCJA: Odpowiedź Erica Lipperta (sparafrazowana): bez konkretnego powodu, z wyjątkiem uniknięcia złożoności zarówno w języku, jak i kompilatorze dla przypadku użycia, który nie wnosi dużej wartości.

Jon Skeet
źródło
139
„z wyjątkiem uniknięcia złożoności zarówno w języku, jak i kompilatorze” ... i to od ludzi, którzy dają nam ko i
sprzeczność
254
„przypadek użycia, który nie wnosi dużej wartości”? To subiektywna opinia, może mi przynieść dużą wartość!
Jon Kruger,
34
Najbardziej niepokoi mnie to, że nie mam tej funkcji, to niemożność robienia rzeczy takich jak [PropertyReference (x => x.SomeProperty)]. Zamiast tego potrzebujesz magicznych ciągów znaków i typeof (), które moim zdaniem są do bani.
Asbjørn Ulsberg
13
@John: Myślę, że zdecydowanie nie doceniasz kosztów projektowania, określania, wdrażania i testowania nowej funkcji językowej.
Jon Skeet,
14
Chciałbym tylko dodać w obronie @ Timwi, że nie jest to jedyne miejsce, o którym się dyskutuje , a poglądy 13K na to pytanie sugerują zdrowy poziom zainteresowania. Również: dzięki za uzyskanie autorytatywnej odpowiedzi, Jon.
Jordan Gray
84

Atrybut ozdabia klasę w czasie kompilacji, ale klasa ogólna nie otrzymuje informacji o ostatecznym typie do czasu uruchomienia. Ponieważ atrybut może wpływać na kompilację, musi być „kompletny” w czasie kompilacji.

Więcej informacji można znaleźć w tym artykule MSDN .

GalacticCowboy
źródło
3
Artykuł potwierdza, że ​​nie są one możliwe, ale bez powodu. Rozumiem koncepcyjnie twoją odpowiedź. Czy znasz już oficjalną dokumentację dotyczącą tego problemu?
Bryan Watts
2
Artykuł dotyczy faktu, że IL nadal zawiera ogólne symbole zastępcze, które są zastępowane rzeczywistym typem w czasie wykonywania. Resztę wywnioskowałem ... :)
GalacticCowboy
1
Ze względu na swoją wartość VB wymusza to samo ograniczenie: „Klasy, które są ogólne lub zawarte w typie ogólnym, nie mogą dziedziczyć po klasie atrybutów”.
GalacticCowboy
1
ECMA-334, sekcja 14.16 mówi: „Wyrażenia stałe są wymagane w kontekstach wymienionych poniżej, a jest to zaznaczone w gramatyce za pomocą wyrażenia stałego. W tych kontekstach występuje błąd czasu kompilacji, jeśli wyrażenie nie może być w pełni ocenione podczas kompilacji czas." Atrybuty znajdują się na liście.
GalacticCowboy
4
Wydaje się, że przeczy temu inna odpowiedź, która stwierdza, że ​​IL na to zezwoli. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
21

Nie wiem, dlaczego jest to niedozwolone, ale jest to jedno z możliwych obejść

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}
GeekyMonkey
źródło
3
Niestety podczas korzystania z atrybutu tracisz pisanie w czasie kompilacji. Wyobraź sobie, że atrybut tworzy coś ogólnego. Możesz obejść to, ale byłoby miło; to jedna z tych intuicyjnych rzeczy, których nie możesz zrobić, na przykład wariancja (obecnie).
Bryan Watts,
14
Niestety, próbując tego nie robić, znalazłem to SO pytanie. Chyba po prostu będę musiał poradzić sobie z typofem. Naprawdę wydaje się, że ilke jest brudnym słowem kluczowym teraz, gdy istnieją już leki generyczne od tak dawna.
Chris Marisic
13

Nie jest to naprawdę ogólne i nadal musisz napisać konkretną klasę atrybutów dla każdego typu, ale możesz być w stanie użyć ogólnego interfejsu podstawowego, aby trochę defensywnie kodować, pisać mniejszy kod niż jest to wymagane, uzyskać korzyści z polimorfizmu itp.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}
nawfal
źródło
8

To jest bardzo dobre pytanie. Z mojego doświadczenia z atrybutami, myślę, że ograniczenie jest na miejscu, bo kiedy zastanawia się nad atrybutem byłoby stworzyć warunki, w których będzie trzeba sprawdzić dla wszystkich możliwych permutacji typu: typeof(Validates<string>),typeof(Validates<SomeCustomType>) , etc ...

Moim zdaniem, jeśli wymagane jest niestandardowe sprawdzenie poprawności w zależności od typu, atrybut może nie być najlepszym podejściem.

Być może lepszym podejściem byłaby klasa walidacyjna, która przyjmuje parametr a SomeCustomValidationDelegatelub a ISomeCustomValidatorjako parametr.

ichiban
źródło
Zgadzam się z Tobą. Mam to pytanie od dłuższego czasu i obecnie buduję system sprawdzania poprawności. Użyłem mojej obecnej terminologii, aby zadać pytanie, ale nie mam zamiaru wdrażać podejścia opartego na tym mechanizmie.
Bryan Watts
Natknąłem się na to, pracując nad projektem dla tego samego celu: walidacji. Próbuję to zrobić w sposób, który jest łatwy do analizy zarówno automatycznie (np. Możesz wygenerować raport opisujący walidację w aplikacji do potwierdzenia), jak i przez człowieka wizualizującego kod. Jeśli nie atrybuty, nie jestem pewien, jakie byłoby najlepsze rozwiązanie ... Mogę nadal wypróbować projekt atrybutu, ale ręcznie zadeklarować atrybuty specyficzne dla typu. To trochę więcej pracy, ale celem jest wiarygodność znajomości reguł sprawdzania poprawności (i możliwość zgłoszenia ich w celu potwierdzenia).
bambams
4
można sprawdzić przed definicji typu rodzajowego (tj typeof (zatwierdza <>)) ...
Melvyn
5

Obecnie nie jest to funkcja języka C #, jednak wiele dyskusji dotyczy oficjalnego repozytorium języka C # .

Z niektórych notatek ze spotkania :

Mimo że działałoby to w zasadzie, w większości wersji środowiska uruchomieniowego występują błędy, przez co nie działałby poprawnie (nigdy nie został wykorzystany).

Potrzebujemy mechanizmu, aby zrozumieć, na jakim docelowym środowisku wykonawczym działa. Potrzebujemy tego do wielu rzeczy i obecnie na to patrzymy. Do tego czasu nie możemy tego znieść.

Kandydat na główną wersję C #, jeśli uda nam się poradzić sobie z wystarczającą liczbą wersji środowiska wykonawczego.

Owen Pauling
źródło
1

Moje obejście jest mniej więcej takie:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }
razon
źródło