I read the Observer example you posted, and I made my own implementation of it.
So, it doesn't exactly match your IObserver structure, but it is very similar.
1) Create a blank solution. Create 3 projects: 1 console application called 'Service' , 1 console application called 'Library' and 1 class library called 'Library'
2) Add reference to Systen.Runtime.Remoting in Service and Client
3) Add reference to Library in Service and Client
4) Add 4 classes to Library: Observer, ConcreteObserver, Subject, ConcreteSubject
using System;
using System.Collections.Generic;
using System.Text;
namespace Library
{
public abstract class Observer : MarshalByRefObject
{
public abstract void Update();
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Library
{
public class ConcreteObserver: Observer
{
private string _observerState;
private ConcreteSubject _subject;
// Constructor
public ConcreteObserver(ConcreteSubject subject)
{
this._subject = subject;
}
public override void Update()
{
_observerState = _subject.SubjectState;
Console.WriteLine("Observer's new state is {0}", _observerState);
}
// Gets or sets subject
public ConcreteSubject Subject
{
get { return _subject; }
set { _subject = value; }
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
namespace Library
{
public class Subject : MarshalByRefObject
{
private List<Observer> _observers = new List<Observer>();
public void Attach(Observer observer)
{
_observers.Add(observer);
}
public void Detach(Observer observer)
{
_observers.Remove(observer);
}
public void Notify()
{
for (int i = _observers.Count - 1; i >= 0; i--)
{
try
{
_observers[i].Update();
}
catch (System.Net.Sockets.SocketException)
{
_observers.RemoveAt(i);
Console.WriteLine("Client was removed");
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Library
{
public class ConcreteSubject: Subject
{
private string _subjectState;
// Gets or sets subject state
public string SubjectState
{
get { return _subjectState; }
set { _subjectState = value; }
}
}
}
5) Add an app.config to the Service project
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="Singleton" type="Library.ConcreteSubject, Library" objectUri="ConcreteSubject.rem" />
</service>
<channels>
<channel ref="tcp" port="8085">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
6) Alter the program.cs for the Service project
using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Threading;
using Library;
namespace Service
{
class Program
{
private static ConcreteSubject _concreteSubject;
static void Main(string[] args)
{
try
{
RemotingConfiguration.Configure("Service.exe.config", false);
Console.WriteLine("Service is configured");
_concreteSubject = new ConcreteSubject();
RemotingServices.Marshal(_concreteSubject, "ConcreteSubject.rem", typeof(ConcreteSubject));
KeepNotifying();
// Wait for user
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.ReadLine();
}
}
private static void KeepNotifying()
{
while (true)
{
Thread.Sleep(2000);
_concreteSubject.SubjectState = "ABC";
Console.WriteLine("Notifying clients...");
_concreteSubject.Notify();
}
}
}
}
7) Create an app.config in the Client project
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown type="Library.ConcreteSubject, Library" url="tcp://localhost:8085/ConcreteSubject.rem" />
</client>
<channels>
<channel ref="tcp" port="0">
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
8) Alter the program.cs for the Client project
using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Threading;
using Library;
namespace Client
{
class Program
{
private static ConcreteObserver _observer;
static void Main(string[] args)
{
try
{
RemotingConfiguration.Configure("Client.exe.config", false);
Console.WriteLine("Client is configured");
Thread.Sleep(500);
ConcreteSubject s = new ConcreteSubject();
_observer = new ConcreteObserver(s);
s.Attach(_observer);
Console.ReadKey();
// By not detaching, an invalid state should occur.
// s.Detach(_observer);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.ReadLine();
}
}
}
}
9) Richt click solution - properties - set multiple startup projects - Set Client and Service to start
10) Run the program
Notice that the line s.Detach(_observer); has been put in comments
This means that the client does not properly clean up its registration.
So, if both programs are running, you see that the client is writing output, and the service tells when it is writing to clients.
Now shut down the client, but keep the server running
Notice that the console window says (at the next iteration) 'Client was removed' but at the same time, it keeps notifying other clients.
Now, uncomment the line s.Detach(_observer); and try the same procedureagain.
This time the client has unregistered itself and the server does not need to clean up.
You can see this because when you close the client (by pressingRETURN twice) there isNO line that says 'Client was removed'.
So, as a conclusion:
Whether or not the client cleans up after itself, it is always removed from the service if it is disconnected.
P.S. It appears you have to catch a SocketException instead of a RemotingException, so I was mistaken about that.
Hopefully this helps,
Kind regards,
Stefan