Search code examples
.net-coresystem.reactivefilesystemwatcher

How to create a `Observable` which observes both `Renamed` and `Changed` events from `FileSystemWatcher`


I want to create a Reactive Observable, which can observe Renamed and Changed at the same time.

I'm monitoring a folder for modified and renamed files with FileSystemWatcher. I only want to handle the event after it has settled down (because sometimes Changed event may be raised multiple times). So I choose the Throttle function of .net's Reactive Extension.

I can manage to do the work by observing a single Changed event, but can't work out how to observe Changed and Renamed in a single Observable.

Here's what I've done for a single Changed event:

var watcher = new FileSystemWatcher();
watcher.Path = "d:\\test";
var observable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                            ev => watcher.Changed += ev,
                            ev => watcher.Changed -= ev
);

var d = observable.Throttle(TimeSpan.FromSeconds(5))
                    .Subscribe(
                        a => Console.WriteLine($"Watcher type:{a.EventArgs.ChangeType }, file:{a.EventArgs.Name }, sender: {a.Sender}"),
                        ex => Console.WriteLine("observer error:" + ex.ToString()),
                        () => Console.WriteLine("observer complete.")
                );

But when I want to watch for both the events, VS tells me that FileSystemEventHandler and RenamedEventHandler are not compatible (though RenamedEventArgs can be safely cast to FileSystemEventArgs):

var observable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                            ev => { 
                            watcher.Changed += ev; 
                            //Error
                            watcher.Renamed += ev; 
                            },
                            ev => { 
                            watcher.Changed -= ev; 
                            //Error
                            watcher.Renamed -= ev; 
                            });

I know I can convert the ev to a compatible delegate , but I'm wondering how can I record that delegate and remove it from the event? Because I read that anonymous delegate can't be removed from event unless record it at register time (as described in (this question)[Adding and Removing Anonymous Event Handler). Aslo, doing it this way same not elegant at all...

Func<FileSystemEventHandler, RenamedEventHandler> renameDelegate = (FileSystemEventHandler h) =>
                {
                    return (object sender, RenamedEventArgs args) => h(sender, args);
                };
var observable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                            ev => { watcher.Changed += ev; 
                                    //registered delegate
                                    watcher.Renamed += renameDelegate(ev); 
                                    },
                            ev => { watcher.Changed -= ev; 
                                    //this seems not to be identical with the one registered
                                    watcher.Renamed -= renameDelegate(ev); });
                );

So how can I observer both Changed and Renamed event in the same Observable?

EDIT 1

Thanks to @Enigmativity 's answer, my final code goes like below(sample code):

var o1 = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                            ev => watcher.Changed += ev,
                            ev => watcher.Changed -= ev);
var o2 = Observable.FromEventPattern<RenamedEventHandler, RenamedEventArgs>(
                            ev => watcher.Renamed += ev,
                            ev => watcher.Renamed -= ev)
                    .Select(x => new System.Reactive.EventPattern<FileSystemEventArgs>(x.Sender, x.EventArgs));

var o = Observable.Merge(o1, o2);

var d = o.Throttle(TimeSpan.FromSeconds(5))
                    .Subscribe(
                        a => Console.WriteLine($"Watcher type:{a.EventArgs.ChangeType }, file:{a.EventArgs.Name }, sender: {a.Sender}"),
                        ex => Console.WriteLine("observer error:" + ex.ToString()),
                        () => Console.WriteLine("observer complete.")

Solution

  • Here's how you do it:

    var observable =
        Observable
            .Using(
                () => new FileSystemWatcher() { Path = "d:\\test" },
                watcher =>
                    Observable
                        .Merge(
                            Observable
                                .FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                                    ev => watcher.Changed += ev,
                                    ev => watcher.Changed -= ev)
                                .Select(x => new
                                {
                                    x.EventArgs.Name,
                                    x.EventArgs.FullPath,
                                    x.EventArgs.ChangeType,
                                    OldName = (string)null,
                                    OldFullPath = (string)null,
                                }),
                            Observable
                                .FromEventPattern<RenamedEventHandler, RenamedEventArgs>(
                                    ev => watcher.Renamed += ev,
                                    ev => watcher.Renamed -= ev)
                                .Select(x => new
                                {
                                    x.EventArgs.Name,
                                    x.EventArgs.FullPath,
                                    x.EventArgs.ChangeType,
                                    x.EventArgs.OldName,
                                    x.EventArgs.OldFullPath
                                })));