IObservableインターフェイス

IObservable(T) インターフェイス (System)
IObserver(T) インターフェイス (System)
.NET Framework 4から追加されたこれらのインターフェイスを使ったサンプルを作成していました。
オブザーバーパターンは一方通行の通知を行うパターンだというのに、チャットアプリケーションを作ってしまったため
クライアントウィンドウがごちゃごちゃしています。



配信側、受信側クラス

public class ChatHost : IObservable<ChatMessage>
{
    protected List<IObserver<ChatMessage>> observers = new List<IObserver<ChatMessage>>();

    public ChatHost()
    {
    }

    public IDisposable Subscribe(IObserver<ChatMessage> observer)
    {
        observers.Add(observer);
        return new _ChatMessage(this, observer);
    }

    private class _ChatMessage : IDisposable
    {
        private ChatHost parent;
        private IObserver<ChatMessage> me;

        public _ChatMessage(ChatHost parent, IObserver<ChatMessage> me)
        {
            this.parent = parent;
            this.me = me;
        }

        public void Dispose()
        {
            if (parent != null && parent.observers.Contains(me))
                parent.observers.Remove(me);
        }
    }

    public void AddMessage(ChatMessage msg)
    {
        if (msg == null)
        {
            foreach (var o in observers)
            {
                o.OnError(new ChatException()
                {
                    Msg = new ChatMessage()
                    {
                        From = "SYSTEM",
                        Message = "無言メッセージが送信されました。送ったのは誰だ!!",
                        SendAt = DateTime.Now
                    }
                });
            }
            return;
        }

        foreach (var o in observers)
        {
            o.OnNext(msg);
        }
    }

    public void EndChat()
    {
        foreach (var o in observers)
        {
            o.OnNext(new ChatMessage()
            {
                From = "SYSTEM",
                Message = "今日はお開きです!!",
                SendAt = DateTime.Now
            });
        }

        int obsCount = observers.Count;
        for (int i = obsCount - 1; i >= 0; i--)
        {
            observers[i].OnCompleted();
        }
    }
}

public class ChatException : Exception
{
    public ChatMessage Msg
    {
        get;
        set;
    }

    public ChatException()
        : base()
    {
    }
}


public class ChatClient : IObserver<ChatMessage>
{
    private IDisposable unsubscriber;
    private ObservableCollection<ChatMessage> msgItemList;

    public ChatClient(ObservableCollection<ChatMessage> msgItemList)
    {
        this.msgItemList = msgItemList;
    }

    public virtual void Subscribe(IObservable<ChatMessage> provider)
    {
        if (provider != null)
            unsubscriber = provider.Subscribe(this);
    }

    public void OnCompleted()
    {
        if(unsubscriber != null)
            unsubscriber.Dispose();
    }

    public void OnError(Exception error)
    {
        var charError = error as ChatException;
        if (charError == null)
            return;

        msgItemList.Add(charError.Msg);
    }

    public void OnNext(ChatMessage value)
    {
        msgItemList.Add(new ChatMessage()
        {
            From = value.From,
            Message = value.Message,
            SendAt = value.SendAt
        });
    }
}


メインウィンドウ。クライアント画面表示やチャットセッションの切断をおこなう。

<Window x:Class="ObservableSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button x:Name="btnAddClient" Click="btnAddClient_Click" Margin="10">クライアント追加</Button>
        <Button x:Name="btnEnd" Margin="10" Click="btnEnd_Click">終了</Button>
    </StackPanel>
</Window>
public partial class MainWindow : Window
{
    private ChatHost provider = new ChatHost();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnAddClient_Click(object sender, RoutedEventArgs e)
    {
        var window = new Window1(provider);
        window.Show();
    }

    private void btnEnd_Click(object sender, RoutedEventArgs e)
    {
        provider.EndChat();
    }
}


クライアントウィンドウ。

<Window x:Class="ObservableSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="356" Width="518" Loaded="Window_Loaded" Closed="Window_Closed">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="8*" />
            <RowDefinition Height="2*" />
        </Grid.RowDefinitions>
        
        <ListView Grid.Row="0" x:Name="listview1">
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding Path=From}" Header="送信者" Width="100" />
                    <GridViewColumn DisplayMemberBinding="{Binding Path=Message}" Header="メッセージ" Width="150" />
                    <GridViewColumn DisplayMemberBinding="{Binding Path=SendAt}" Header="日付" Width="200" />
                </GridView>
            </ListView.View>
        </ListView>

        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <TextBox x:Name="textbox1" Width="300" Margin="5"></TextBox>
            <Button x:Name="button1" Width="100" Margin="5" Click="button1_Click">送信</Button>
        </StackPanel>
    </Grid>
</Window>
public partial class Window1 : Window
{
    private ObservableCollection<ChatMessage> msgs = new ObservableCollection<ChatMessage>();
    private ChatHost provider;
    private ChatClient observer;
    private string userName = "";

    public Window1()
    {
        InitializeComponent();
    }

    public Window1(ChatHost provider)
    {
        this.provider = provider;
        this.observer = new ChatClient(msgs);
        observer.Subscribe(provider);

        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        listview1.ItemsSource = msgs;

        //ユーザー名入力ダイアログを表示、ユーザー名設定。
        var dialog = new Window2();
        dialog.Owner = this;
        dialog.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
        dialog.ShowInTaskbar = false;
        dialog.ShowDialog();
        this.userName = dialog.UserName;
        this.Title = userName;
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        if (textbox1.Text.Trim().Length == 0)
        {
            provider.AddMessage(null);
        }
        else
        {
            provider.AddMessage(new ChatMessage()
            {
                From = userName,
                Message = textbox1.Text,
                SendAt = DateTime.Now
            });
        }
    }

    private void Window_Closed(object sender, EventArgs e)
    {
        observer.OnCompleted();
    }
}