Jak C# Events działa za kulisami?

Używam C#,. Net 3.5. Rozumiem, jak wykorzystać zdarzenia, jak je zadeklarować w mojej klasie, jak podłączyć je z innego miejsca itp. Zmyślony przykład:

public class MyList
{
    private List<string> m_Strings = new List<string>();
    public EventHandler<EventArgs> ElementAddedEvent;

    public void Add(string value)
    {
        m_Strings.Add(value);
        if (ElementAddedEvent != null)
            ElementAddedEvent(value, EventArgs.Empty);
    }
}

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        MyList tmp = new MyList();
        tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);
        tmp.Add("test");
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Jednak to, co robię nie Rozumiem, jest wtedy, gdy deklaruje się obsługę zdarzenia

public EventHandler<EventArgs> ElementAddedEvent;

Nigdy nie jest inicjowany - więc co dokładnie jest ElementAddedEvent? Na co to wskazuje? Poniżej nie będzie działać, ponieważ program EventHandler nigdy nie jest inicjowany:

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        EventHandler<EventArgs> somethingHappend;
        somethingHappend += new EventHandler<EventArgs>(Fired);
        somethingHappend(this, EventArgs.Empty);
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Zauważam, że istnieje EventHandler.CreateDelegate(...), ale wszystkie sygnatury metod sugerują, że jest to używane tylko do dołączania delegatów do już istniejącego programu EventHandler poprzez typowy ElementAddedEvent + = new EventHandler (MyMethod).

Nie wiem, czy to, co próbuję zrobić, pomoże... ale ostatecznie chciałbym wymyślić abstrakcyjny rodzic DataContext w LINQ, którego dzieci mogą zarejestrować typy tabel, które chcą "obserwować", więc mogę mieć zdarzenia takie jak BeforeUpdate i AfterUpdate, ale specyficzne dla typów. Coś takiego:
public class BaseDataContext : DataContext
{
    private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();

    public static void Observe(Type type)
    {
        if (m_ObservedTypes.ContainsKey(type) == false)
        {
            m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());

            EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);
        }
    }

    public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events
    {
        get { return m_ObservedTypes; }
    }
}


public class MyClass
{
    public MyClass()
    {
        BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);
    }

    public void OnUserUpdated(object sender, EventArgs args)
    {
        // do something
    }
}

Myślenie o tym uświadomiło mi, że nie bardzo rozumiem, co dzieje się pod hod z wydarzeniami - i chciałbym to zrozumieć:)

Author: Matt, 2008-10-18

2 answers

Napisałem to w sporej ilości szczegółów w Artykuł , ale oto podsumowanie, zakładając, że jesteś w miarę zadowolony z delegatów siebie:

  • zdarzenie jest tylko metodą "Dodaj" i metodą "Usuń", tak samo jak właściwość jest tak naprawdę tylko metodą" get "I metodą" set". (W rzeczywistości CLI pozwala również na metodę "raise / fire", ale C# nigdy tego nie generuje.) Metadane opisują Zdarzenie za pomocą odniesień do metod.
  • Kiedy deklarujesz wydarzenie polowe (podobnie jak ElementAddedEvent) kompilator generuje metody i prywatne pole (tego samego typu co delegat). W obrębie klasy, gdy odnosisz się do ElementAddedEvent odnosisz się do pola. Poza klasą, masz na myśli pole.
  • gdy ktoś subskrybuje Zdarzenie (z operatorem+=), które wywołuje metodę add. Gdy wypisują się (za pomocą operatora -=), który wywołuje usunąć
  • W przypadku zdarzeń podobnych do pól istnieje pewna synchronizacja, ale poza tym Dodaj/usuń po prostu wywołaj delegata.połączyć/Usuń , aby zmienić wartość automatycznie wygenerowanego pola. Obie te operacje przypisują pole zapasowe-pamiętaj, że delegaci są niezmienni. Innymi słowy, kod autogeneracyjny jest bardzo podobny do tego:

    // Backing field
    // The underscores just make it simpler to see what's going on here.
    // In the rest of your source code for this class, if you refer to
    // ElementAddedEvent, you're really referring to this field.
    private EventHandler<EventArgs> __ElementAddedEvent;
    
    // Actual event
    public EventHandler<EventArgs> ElementAddedEvent
    {
        add
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent += value;
                __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
            }
        }
        remove
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent -= value;
                __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
            }
        }
    }
    
  • Wartość początkowa wygenerowanego pola w Twoim przypadku to null - i zawsze będzie to null ponownie, jeśli wszyscy subskrybenci zostaną usunięci, ponieważ jest to zachowanie delegata.Usunąć

  • Jeśli chcesz, aby obsługa" no-op " subskrybowała Twoje wydarzenie, aby uniknąć sprawdzenia nieważności, możesz to zrobić:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    delegate {} jest tylko anonimową metodą, która nie dba o swoje parametry i nic nie robi.

Jeśli jest coś jeszcze niejasnego, proszę zapytać, a ja postaram się pomóc!

 65
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 20:41:05

Pod maską, wydarzenia są po prostu delegatami ze specjalnymi konwencjami wywołującymi. (Na przykład nie musisz sprawdzać nieważności przed zgłoszeniem zdarzenia.)

W pseudokodzie, Event.Wywołanie() rozkłada się w następujący sposób:

If Event Has Listeners Wywołaj każdego słuchacza synchronicznie w tym wątku w dowolnej kolejności.

Ponieważ zdarzenia są multicastem, będą miały zero lub więcej słuchaczy, trzymanych w kolekcji. CLR zapętli je, wywołując każdy w dowolnym spokój.

Należy pamiętać, że procedury obsługi zdarzeń są wykonywane w tym samym wątku, w którym zdarzenie jest podnoszone. Częstym błędem umysłowym jest myślenie o nich jako o zrodzeniu nowego wątku. Nie.

 -3
Author: Yes - that Jake.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 20:06:48