Metodtips för mönstret för observatörsdesign

I .NET implementeras mönstret för observatörsdesign som en uppsättning gränssnitt. Gränssnittet System.IObservable<T> representerar dataleverantören, som också ansvarar för att tillhandahålla en IDisposable implementering som gör att observatörer kan avbryta prenumerationen på meddelanden. Gränssnittet System.IObserver<T> representerar övervakaren.

I den här artikeln beskrivs metodtips som du bör följa när du implementerar designmönstret för övervakare med dessa gränssnitt.

Överväg alternativ innan du implementerar

Gränssnitten IObservable<T> och IObserver<T> passar bra för push-baserade meddelandescenarier, men andra .NET mönster kan passa bättre. Använd händelser för enkla meddelanden i ett enda program. För asynkrona pull-baserade sekvenser där konsumenten styr takten använder du IAsyncEnumerable<T>. För producent-/konsumentmönster med bakåttryck använder du System.Threading.Channels. För komplex händelsesammansättning, filtrering och transformering använder du paketet System.Reactive (Rx.NET) i stället för att implementera IObservable<T> direkt. Mer information finns i Observer design pattern (Observatörsdesignmönster).

Använda en separat typ för meddelandedata

Objektet som innehåller de data som providern skickar till sina observatörer motsvarar den generiska typparametern IObservable<T> för och IObserver<T>. Även om det här objektet kan vara detsamma som implementeringen IObservable<T> definierar du det som en separat typ. En dedikerad datatyp håller leverantörens ansvar åtskilda från meddelandenyttolasten och gör API:et enklare att utveckla.

Förlita dig inte på meddelandeordning

I vilken ordning observatörer tar emot meddelanden definieras inte. Leverantören kan använda valfri metod för att fastställa ordningen, så skriv inte observatörer som är beroende av att meddelas före eller efter en annan observatör.

Gör Subscribe och Dispose trådsäkra

Vanligtvis implementerar en provider metoden IObservable<T>.Subscribe genom att lägga till en observatör i en prenumerantlista som representeras av ett samlingsobjekt, och implementerar metoden IDisposable.Dispose genom att ta bort observatören från den listan. En övervakare kan anropa dessa metoder när som helst. Provider-/observatörskontraktet anger inte vem som ansvarar för att avbryta prenumerationen IObserver<T>.OnCompleted efter återanropsmetoden, så både providern och övervakaren kan försöka ta bort samma medlem från listan.

Undvik kapplöpningstillstånd genom att göra både metoden Subscribe och metoden Dispose trådsäkra. Detta innebär vanligtvis att du använder en samtidig samling eller ett lås. Implementeringar som inte är trådsäkra bör uttryckligen dokumentera att de inte är det.

Dokumentera eventuella extra kontraktsgarantier

Ange eventuella extra garantier i ett lager ovanpå provider-/observatörskontraktet. När du ställer andra krav kan du tydligt kalla ut dem så att användarna inte blir förvirrade över observatörskontraktet.

Hantera undantag som information

På grund av den lösa kopplingen mellan en dataprovider och en övervakare är undantag i övervakningsdesignmönstret avsedda att vara informationsmässiga. Den här egenskapen påverkar hur leverantörer och observatörer hanterar undantag.

Anropa OnError endast när uppdateringar inte kan fortsätta

Metoden OnError är avsedd som ett informationsmeddelande till observatörer, ungefär som IObserver<T>.OnNext metoden. Metoden OnNext ger dock en observatör aktuella eller uppdaterade data, medan metoden OnError anger att leverantören inte kan tillhandahålla giltiga data.

Följ dessa metodtips när du hanterar undantag och anropar OnError metoden:

  • Providern måste hantera sina egna undantag om den har några specifika krav.
  • Leverantören bör inte förvänta sig eller kräva att observatörer hanterar undantag på något visst sätt.
  • Providern bör anropa OnError metoden när den hanterar ett undantag som äventyrar dess möjlighet att tillhandahålla uppdateringar. Skicka information om sådana undantag till övervakaren. I andra fall behöver du inte meddela observatörer om ett undantag.

När leverantören har anropat OnError metoden eller IObserver<T>.OnCompleted bör det inte finnas några ytterligare meddelanden och leverantören kan avbryta prenumerationen på sina observatörer. Observatörerna kan dock också avregistrera sig när som helst, både före och efter att de får en OnError eller IObserver<T>.OnCompleted avisering. Mönstret för observatörsdesign avgör inte om providern eller övervakaren ansvarar för att avbryta prenumerationen, så båda kan försöka avbryta prenumerationen. När observatörer avregistrerar sig tas de vanligtvis bort från en prenumerantsamling. I ett entrådat program IDisposable.Dispose bör implementeringen se till att en objektreferens är giltig och att objektet är medlem i prenumerantsamlingen innan det försöker ta bort objektet. I ett flertrådat program använder du ett lås för att skydda observatörssamlingen.

Behandla OnError-meddelanden som information hos observatörer

När en övervakare får ett felmeddelande från en leverantör bör övervakaren behandla undantaget som information och bör inte vara skyldig att vidta några särskilda åtgärder.

Följ dessa metodtips när du svarar på ett OnError metodanrop från en provider:

  • Generera inte undantag från gränssnittsimplementeringar som OnNext eller OnError. Om observatören genererar undantag bör du räkna med att dessa undantag förblir ohanterade.
  • För att bevara anropsstacken bör en observatör som vill kasta ett Exception-objekt som skickades till dess metod OnError paketera in undantaget innan det kastas vidare. Använd ett standardfelobjekt för det här ändamålet.

Avregistrera inte i metoden Prenumerera

Försök inte avregistrera i IObservable<T>.Subscribe -metoden eftersom det kan resultera i en null-referens.

Anslut en övervakare till en enskild leverantör

Även om du kan koppla en övervakare till flera leverantörer är det rekommenderade mönstret att endast koppla en IObserver<T> instans till en IObservable<T> instans.