2008-10-10 32 views
69

Czy możliwe jest zdefiniowanie klasy w języku C#, tak żeC# rodzajowych nie pozwoli Delegat Rodzaj Ograniczenia

class GenericCollection<T> : SomeBaseCollection<T> where T : Delegate 

nie mogłem dla życia mnie zrealizować tę ostatnią noc w .NET 3.5. Próbowałem za pomocą

delegate, Delegate, Action<T> and Func<T, T>

Wydaje mi się, że powinno to być dopuszczalna w jakiś sposób. Próbuję wdrożyć własną EventQueue.

Skończyłem właśnie robiąc to [prymitywny aproksymacja].

internal delegate void DWork(); 

class EventQueue { 
    private Queue<DWork> eventq; 
} 

Ale potem tracę możliwość ponownego użycia tej samej definicji dla różnych typów funkcji.

Myśli?

Odpowiedz

64

Liczba klas jest niedostępna jako ogólne ograniczenia - Enum to drugie.

Dla delegatów, najbliżej można dostać to „class”, być może przy użyciu odbicia do sprawdzenia (na przykład w statycznym konstruktorze), że T jest delegatem:

static GenericCollection() 
{ 
    if (!typeof(T).IsSubclassOf(typeof(Delegate))) 
    { 
     throw new InvalidOperationException(typeof(T).Name + " is not a delegate type"); 
    } 
} 
+7

+1 dla: 1) za pomocą konstruktora statycznego i 2) zawierającego szczegółowy komunikat z powodu dziwnych warunków debugowania otaczającego inicjowanie typu. –

+6

@MarcGravell: Nie rzuca wyjątku w statycznym inicjalizatorze naruszającym 'CA1065: Nie zgłaszaj wyjątków w nieoczekiwanych lokalizacjach' ... Zawsze byłem przy założeniu, że powinieneś użyć niestandardowej reguły analizy kodu, aby znaleźć niewłaściwe zastosowania twojego klasa, która zazwyczaj nie jest dostępna w czasie wykonywania. –

13

Edycja : Niektóre proponowane obejścia są proponowane w tych artykułach:

http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html

http://jacobcarpenters.blogspot.com/2006_11_01_archive.html


Z C# 2.0 specification możemy odczytać (20,7, ograniczenia):

Klasa typu ograniczenia muszą spełniać następujące zasady:

  • typ musi być typu klasy.
  • Typ nie może być plombowany.
  • Typ nie może być jednym z następujących typów: System.Array, System.Delegate, System.Enum lub System.ValueType.
  • Typ nie może być obiektem. Ponieważ wszystkie typy pochodzą od obiektu, takie ograniczenie nie miałoby żadnego skutku, gdyby było dozwolone.
  • Maksymalnie jednym ograniczeniem dla danego parametru typu może być typ klasy.

I rzeczywiście VS2008 wypluwa błąd:

error CS0702: Constraint cannot be special class 'System.Delegate' 

Dla informacji i dochodzenia w tej sprawie przeczytać here.

3

Delegat obsługuje już łańcuchowanie. Czy to nie spełnia twoich potrzeb?

public class EventQueueTests 
{ 
    public void Test1() 
    { 
     Action myAction =() => Console.WriteLine("foo"); 
     myAction +=() => Console.WriteLine("bar"); 

     myAction(); 
     //foo 
     //bar 
    } 

    public void Test2() 
    { 
     Action<int> myAction = x => Console.WriteLine("foo {0}", x); 
     myAction += x => Console.WriteLine("bar {0}", x); 
     myAction(3); 
     //foo 3 
     //bar 3 
    } 

    public void Test3() 
    { 
     Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; }; 
     myFunc += x => { Console.WriteLine("bar {0}", x); return x + 1; }; 
     int y = myFunc(3); 
     Console.WriteLine(y); 

     //foo 3 
     //bar 3 
     //4 
    } 

    public void Test4() 
    { 
     Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; }; 
     Func<int, int> myNextFunc = x => { x = myFunc(x); Console.WriteLine("bar {0}", x); return x + 1; }; 
     int y = myNextFunc(3); 
     Console.WriteLine(y); 

     //foo 3 
     //bar 5 
     //6 
    } 

} 
+0

To nie jest tak naprawdę funkcjonalność, której szukam ... Próbowałem wprowadzić ograniczenie typu na mojej klasie ogólnej ... –

3

natknąłem sytuacji gdzie potrzebne do czynienia z Delegate wewnętrznie ale chciałem rodzajowe ograniczenie. W szczególności chciałem dodać obsługę zdarzeń za pomocą refleksji, ale chciałem użyć ogólnego argumentu dla delegata. Poniższy kod nie działa, ponieważ „Handler” to typ zmiennej, a kompilator nie rzuci Handler do Delegate:

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
    c.GetType().GetEvent(eventName).AddEventHandler(c, (Delegate) d); 
} 

Jednak można przekazać do funkcji, które wykonuje konwersję dla Ciebie. convert zajmuje Handler argument i zwraca Delegate:

public void AddHandler<Handler>(Control c, string eventName, 
        Func<Delegate, Handler> convert, Handler d) { 
     c.GetType().GetEvent(eventName).AddEventHandler(c, convert(d)); 
} 

Teraz kompilator jest szczęśliwy. Wywołanie metody jest łatwe. Na przykład, dołączenie do wydarzenia KeyPress na Windows Forms kontroli:

AddHandler<KeyEventHandler>(someControl, 
      "KeyPress", 
      (h) => (KeyEventHandler) h, 
      SomeControl_KeyPress); 

gdzie SomeControl_KeyPress jest celem zdarzenia. Kluczem jest konwerter lambda - nie działa, ale przekonuje kompilatora, któremu dałeś prawidłowego delegata.

(Rozpocznij 280Z28) @ Justin: Dlaczego nie użyć tego?

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
    c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate); 
} 

(End 280Z28)

+1

@Justin: Edytowałem twoją odpowiedź, aby umieścić mój komentarz na końcu, ponieważ ma on blok kodu. –

+1

Dobry połów. To działa bardzo ładnie. –

10

Jeśli są chętni do podjęcia kompilacji zależność czasową na IL Weaver Można to zrobić z Fody.

Korzystanie z tego dodatku do Fody https://github.com/Fody/ExtraConstraints

Twój kod może wyglądać następująco

public class Sample 
{ 
    public void MethodWithDelegateConstraint<[DelegateConstraint] T>() 
    {   
    } 
    public void MethodWithEnumConstraint<[EnumConstraint] T>() 
    { 
    } 
} 

I być skompilowane do tego

public class Sample 
{ 
    public void MethodWithDelegateConstraint<T>() where T: Delegate 
    { 
    } 

    public void MethodWithEnumConstraint<T>() where T: struct, Enum 
    { 
    } 
} 
+0

Uszkodzony link. Czy masz aktualny? –

+0

@JustinMorgan updated – Simon

2

Jak wspomniano powyżej, nie można mieć Delegaci i enum jako ogólne ograniczenie. System.Object i System.ValueType również nie mogą być używane jako ogólne ograniczenie.

Praca wokół może być, jeśli skonstruujesz odpowiednie połączenie w sobie IL. Będzie działać dobrze.

Oto dobry przykład autorstwa Jona Skeeta.

http://code.google.com/p/unconstrained-melody/

Brałem moje referencje od Jon Skeet książka C# w Zd 3 edycji.

1

Według MSDN

Błąd kompilatora CS0702

ograniczenie nie może być specjalna klasa 'identyfikator' nie mogą być stosowane jako ograniczeń następujące typy:

  • systemu.Obiektów
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.