Czy istnieje sposób na uzyskanie następującej deklaracji funkcji?
public bool Foo<T>() where T : interface;
to znaczy. gdzie T to typ interfejsu (podobny do where T : class
i struct
).
Obecnie zadowalam się:
public bool Foo<T>() where T : IBase;
Gdzie IBase jest zdefiniowany jako pusty interfejs, który jest dziedziczony przez wszystkie moje niestandardowe interfejsy ... Nie jest to idealne, ale powinno działać ... Dlaczego nie możesz zdefiniować, że typ ogólny musi być interfejsem?
Co jest warte, chcę tego, ponieważ Foo
wykonuje refleksję tam, gdzie potrzebuje typu interfejsu ... Mógłbym przekazać to jako normalny parametr i przeprowadzić niezbędne sprawdzenie w samej funkcji, ale wydawało się to o wiele bardziej bezpieczne (i ja załóżmy, że jest trochę wydajniejszy, ponieważ wszystkie sprawdzenia są wykonywane w czasie kompilacji).
źródło
IBase
- używane w ten sposób - nazywane są interfejsami znaczników . Umożliwiają specjalne zachowania dla „oznaczonych” typów.Odpowiedzi:
Najbliższe co możesz zrobić (poza podejściem do interfejsu podstawowego) to "
where T : class
", co oznacza typ referencyjny. Nie ma składni oznaczającej „dowolny interfejs”.To („
where T : class
”) jest używane na przykład w programie WCF w celu ograniczenia klientów do umów serwisowych (interfejsów).źródło
interface
ograniczenieT
powinno umożliwiać porównywanie odniesień międzyT
dowolnym innym typem odniesienia, ponieważ porównania odniesień są dozwolone między dowolnym interfejsem a prawie każdym innym typem odniesienia, a umożliwienie porównań nawet w tym przypadku nie byłoby problemu.Wiem, że to trochę za późno, ale dla zainteresowanych możesz skorzystać z funkcji sprawdzania czasu pracy.
źródło
Foo(Type type)
.if (new T() is IMyInterface) { }
do sprawdzenia, czy interfejs jest zaimplementowany przez klasę T. Może nie być najbardziej wydajnym, ale działa.No, rzeczywiście, jeśli myśli
class
istruct
oznaczaćclass
ES istruct
S, mylisz się.class
oznacza dowolny rodzaj odniesienia (np zawiera interfejsy zbyt) istruct
oznacza wartość dowolnego typu (na przykładstruct
,enum
).źródło
where T : struct
ograniczeniami dopasowania .class
, ale zadeklarowanie lokalizacji pamięci typu interfejsu w rzeczywistości deklaruje lokalizację pamięci jako odwołanie do klasy, która implementuje ten typ.where T : struct
odpowiadaNotNullableValueTypeConstraint
, czyli musi być typem wartości innym niżNullable<>
. (WięcNullable<>
to typ struct, który nie spełniawhere T : struct
ograniczenie.)Kontynuując odpowiedź Roberta, jest to jeszcze później, ale możesz użyć statycznej klasy pomocniczej, aby sprawdzić w czasie wykonywania tylko raz dla każdego typu:
Zwracam również uwagę, że Twoje rozwiązanie „powinno działać” w rzeczywistości nie działa. Rozważać:
Teraz nic nie stoi na przeszkodzie, aby zadzwonić do Foo:
W
Actual
końcu klasa spełnia toIBase
ograniczenie.źródło
static
Konstruktor nie może byćpublic
, więc to powinno dać błąd kompilacji. Twojastatic
klasa zawiera również metodę instancji, co również jest błędem w czasie kompilacji.Od jakiegoś czasu zastanawiam się nad ograniczeniami związanymi z czasem kompilacji, więc jest to doskonała okazja do uruchomienia koncepcji.
Podstawową ideą jest to, że jeśli nie możesz sprawdzić czasu kompilacji, powinieneś to zrobić jak najwcześniej, czyli w momencie uruchomienia aplikacji. Jeśli wszystkie testy przebiegną pomyślnie, aplikacja będzie działać; jeśli sprawdzenie się nie powiedzie, aplikacja zawiedzie natychmiast.
Zachowanie
Najlepszym możliwym wynikiem jest to, że nasz program nie kompiluje się, jeśli ograniczenia nie są spełnione. Niestety nie jest to możliwe w obecnej implementacji C #.
Następną najlepszą rzeczą jest to, że program ulega awarii w momencie uruchomienia.
Ostatnią opcją jest to, że program ulegnie awarii w momencie trafienia kodu. Jest to domyślne zachowanie platformy .NET. Dla mnie jest to całkowicie nie do przyjęcia.
Wymagania wstępne
Musimy mieć mechanizm ograniczający, więc z braku czegoś lepszego ... użyjmy atrybutu. Atrybut będzie obecny nad ogólnym ograniczeniem, aby sprawdzić, czy spełnia nasze warunki. Jeśli tak się nie stanie, popełniamy brzydki błąd.
To pozwala nam robić takie rzeczy w naszym kodzie:
(Zachowałem
where T:class
tutaj, ponieważ zawsze wolę sprawdzanie czasu kompilacji od sprawdzania w czasie wykonywania)Pozostaje więc tylko jeden problem, który polega na sprawdzeniu, czy wszystkie typy, których używamy, pasują do ograniczenia. Jak trudne może to być?
Rozbijmy to
Typy ogólne są zawsze albo w klasie (/ struct / interface), albo w metodzie.
Wyzwolenie ograniczenia wymaga wykonania jednej z następujących czynności:
W tym miejscu chciałbym stwierdzić, że zawsze należy unikać robienia (4) w jakimkolwiek programie IMO. Niezależnie od tego te kontrole go nie obsługują, ponieważ oznaczałoby to skuteczne rozwiązanie problemu zatrzymania.
Przypadek 1: użycie typu
Przykład:
Przykład 2:
Zasadniczo obejmuje to skanowanie wszystkich typów, dziedziczenia, składowych, parametrów itp., Itd., Itd. Jeśli typ jest typem ogólnym i zawiera ograniczenie, sprawdzamy ograniczenie; jeśli jest to tablica, sprawdzamy typ elementu.
W tym miejscu muszę dodać, że to przełamie fakt, że domyślnie .NET ładuje typy „leniwe”. Skanując wszystkie typy, zmuszamy środowisko uruchomieniowe .NET do załadowania ich wszystkich. W przypadku większości programów nie powinno to stanowić problemu; mimo to, jeśli używasz statycznych inicjatorów w swoim kodzie, możesz napotkać problemy z tym podejściem ... Mimo to nie radziłbym nikomu tego robić (z wyjątkiem takich rzeczy :-), więc nie powinien dawać masz wiele problemów.
Przypadek 2: użycie typu w metodzie
Przykład:
Aby to sprawdzić, mamy tylko jedną opcję: zdekompiluj klasę, sprawdź wszystkie używane tokeny składowe, a jeśli jeden z nich jest typem ogólnym - sprawdź argumenty.
Przypadek 3: Odbicie, ogólna konstrukcja środowiska wykonawczego
Przykład:
Przypuszczam, że teoretycznie można to sprawdzić podobnymi trikami jak w przypadku (2), ale implementacja tego jest znacznie trudniejsza (trzeba sprawdzić, czy
MakeGenericType
jest wywoływana w jakiejś ścieżce kodu). Nie będę tutaj wchodził w szczegóły ...Przypadek 4: odbicie, czas wykonania RTTI
Przykład:
To jest najgorszy scenariusz i jak wyjaśniłem wcześniej, ogólnie zły pomysł IMHO. Tak czy inaczej, nie ma praktycznego sposobu rozwiązania tego problemu za pomocą czeków.
Testowanie partii
Utworzenie programu testującego przypadek (1) i (2) da coś takiego:
Korzystanie z kodu
Cóż, to łatwa część :-)
źródło
Nie można tego zrobić w żadnej wydanej wersji C #, ani w nadchodzącym C # 4.0. Nie jest to też ograniczenie języka C # - w samym środowisku CLR nie ma ograniczenia „interfejsu”.
źródło
Jeśli to możliwe, wybrałem takie rozwiązanie. Działa tylko wtedy, gdy chcesz, aby kilka określonych interfejsów (np. Tych, do których masz dostęp do źródła) było przekazywanych jako parametr ogólny, a nie żaden.
IInterface
.IInterface
W źródle wygląda to tak:
Dowolny interfejs, który chcesz przekazać jako parametr ogólny:
Interfejs:
Klasa, dla której chcesz umieścić ograniczenie typu:
źródło
T
nie jest ograniczony do interfejsów, jest ograniczony do wszystkiego, co implementujeIInterface
- co każdy typ może zrobić, jeśli chce, np.struct Foo : IInterface
Ponieważ TwójIInterface
jest najprawdopodobniej publiczny (w przeciwnym razie wszystko, co go akceptuje, musiałoby być wewnętrzne).To, na co się zdecydowałeś, to najlepsze, co możesz zrobić:
źródło
Próbowałem zrobić coś podobnego i zastosowałem rozwiązanie obejściowe: pomyślałem o niejawnym i jawnym operatorze struktury: Pomysł polega na zawinięciu Type w strukturę, którą można niejawnie przekonwertować na Type.
Oto taka struktura:
public struct InterfaceType {private Type _type;
}
podstawowe zastosowanie:
Musisz wyobrazić sobie swój własny mekanizm wokół tego, ale przykładem może być metoda z parametrem typu InterfaceType zamiast typu
Metoda do przesłonięcia, która powinna zwracać typy interfejsów:
Być może są też rzeczy do zrobienia z lekami generycznymi, ale nie próbowałem
Mam nadzieję, że to pomoże lub daje pomysły :-)
źródło
Rozwiązanie A: Ta kombinacja ograniczeń powinna zagwarantować, że
TInterface
jest to interfejs:Potrzeba tylko jednej struktury
TStruct
jako Świadka, aby to udowodnićTInterface
jest strukturą.Możesz użyć pojedynczej struktury jako świadka dla wszystkich nieogólnych typów:
Rozwiązanie B: Jeśli nie chcesz tworzyć struktur jako świadków, możesz utworzyć interfejs
i użyj ograniczenia:
Wdrożenie dla interfejsów:
To rozwiązuje niektóre problemy, ale wymaga zaufania, którego nikt nie implementuje
ISInterface<T>
dla typów bez interfejsów, ale jest to dość trudne do zrobienia przypadkowo.źródło
Zamiast tego użyj klasy abstrakcyjnej. Więc miałbyś coś takiego:
źródło