C # Generics nie zezwala na ograniczenia typu delegata

79

Czy można w C # zdefiniować taką klasę, że

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

Za całe życie nie mogłem tego zrobić wczoraj w .NET 3.5. Próbowałem użyć

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

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

Skończyło się na tym, że robię to [uwaga na prymitywne przybliżenie].

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?

Nicholas Mancuso
źródło

Odpowiedzi:

66

Szereg klas jest niedostępnych jako ogólne ograniczenia - kolejnym jest Enum.

W przypadku delegatów najbliższym możliwym do uzyskania jest „: class”, być może przy użyciu odbicia do sprawdzenia (na przykład w konstruktorze statycznym), że T jest delegatem:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}
Marc Gravell
źródło
8
+1 dla: 1) użycia konstruktora statycznego i 2) dołączenia szczegółowego komunikatu z powodu dziwnych warunków debugowania otaczających inicjalizację typu.
Sam Harwell
6
@MarcGravell: Nie rzuca wyjątku w statycznym inicjatorze narusza CA1065: Do not raise exceptions in unexpected locations... Zawsze zakładałem , że powinieneś użyć niestandardowej reguły analizy kodu, aby znaleźć nieprawidłowe zastosowania Twojej klasy, które zwykle nie są dostępne w czasie wykonywania.
myermian
3
Począwszy od języka C # 7.3 (wydanego w maju 2018 r.), Dozwolone jest takie ograniczenie where T : Delegate(i ktoś opublikował nową odpowiedź na ten temat poniżej).
Jeppe Stig Nielsen
16

Tak to jest możliwe w C # 7.3, Ograniczenia rodzina powiększyła się Enum, Delegatea unmanagedtypy. Możesz napisać ten kod bez problemu:

void M<D, E, T>(D d, E e, T* t) where D : Delegate where E : Enum where T : unmanaged
    {

    }

Z Dokumentów :

Począwszy od C # 7,3, można użyć niezarządzanego ograniczenia, aby określić, że parametr typu musi być niezarządzanym typem niezarządzanym. Niezarządzane ograniczenie umożliwia pisanie procedur wielokrotnego użytku do pracy z typami, którymi można manipulować jako blokami pamięci

Przydatne linki:

Przyszłość języka C # z Microsoft Build 2018

Co nowego w C # 7.3?

mshwf
źródło
Tak, jest to możliwe w języku C # 7.3 (od maja 2018 r.), A informacje o wersji można znaleźć tutaj .
Jeppe Stig Nielsen
13

Edycja: w tych artykułach zaproponowano niektóre proponowane obejścia:

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

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


Ze specyfikacji C # 2.0 możemy przeczytać (20.7, ograniczenia):

Ograniczenie typu klasy musi spełniać następujące zasady:

  • Typ musi być typem klasy.
  • Typu nie wolno zaplombować.
  • Typ nie może należeć do jednego 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 efektu, gdyby było dozwolone.
  • Co najwyżej jedno ograniczenie dla danego parametru typu może być typem klasy.

I rzeczywiście VS2008 wypluwa błąd:

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

Więcej informacji i dochodzenie w tej sprawie można znaleźć tutaj .

Jorge Ferreira
źródło
10

Jeśli chcesz uzależnić czas kompilacji od IL Weaver, możesz to zrobić za pomocą Fody .

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

Twój kod może wyglądać tak

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

I być skompilowanym do tego

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

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
Szymon
źródło
Uszkodzony link. Czy masz aktualny?
Justin Morgan,
3

Delegat obsługuje już tworzenie łańcuchów. 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
    }

}
Amy B.
źródło
to nie jest tak naprawdę funkcjonalność, której szukam ... Próbowałem wprowadzić ograniczenie typu w mojej klasie ogólnej ...
Nicholas Mancuso
3

Natknąłem się na sytuację, w której musiałem poradzić sobie Delegatewewnętrznie, ale chciałem mieć ogólne ograniczenie. W szczególności chciałem dodać procedurę obsługi zdarzeń za pomocą odbicia, ale chciałem użyć ogólnego argumentu dla delegata. Poniższy kod NIE działa, ponieważ „Handler” jest zmienną typu, a kompilator nie będzie rzutował Handlerna Delegate:

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

Możesz jednak przekazać funkcję, która dokona konwersji za Ciebie. convertprzyjmuje Handlerargument 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łączanie do KeyPresszdarzenia w kontrolce Windows Forms:

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

gdzie SomeControl_KeyPressjest cel zdarzenia. Kluczem jest lambda konwertera - nie działa, ale przekonuje kompilator, któremu nadałeś mu 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); 
} 

(Koniec 280Z28)

Justin Bailey
źródło
1
@Justin: Zredagowałem twoją odpowiedź, aby umieścić mój komentarz na końcu, ponieważ zawiera blok kodu.
Sam Harwell
2

Jak wspomniano powyżej, nie można mieć delegatów i wyliczenia jako ogólnego ograniczenia. System.Objecta System.ValueTypetakże nie może być używane jako ogólne ograniczenie.

Aby obejść ten problem, możesz skonstruować odpowiednie wywołanie w swoim IL. Będzie działać dobrze.

Oto dobry przykład autorstwa Jona Skeeta.

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

Swoje odniesienia zaczerpnąłem z książki Jona Skeeta C # in Depth , 3. wydanie.

maxspan
źródło
1

Według MSDN

Błąd kompilatora CS0702

Ograniczenie nie może być specjalnym „identyfikatorem” klasy Następujących typów nie można używać jako ograniczeń:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
źródło
Dlaczego tutaj powtarzasz pytanie? Nie mówisz nam nic nowego.
Elmue