ReactivePropertyでDataGridの列自動生成を試してみた
ReactivePropertyを使用してDataGridの列の自動生成機能を試した時のメモ
- 列自動生成機能を使用すると、パブリックなプロパティに対応した列が作成される
- プロパティの値が表示されるが、プロパティがReactivePropertyの場合はValueプロパティを表示するようにしたい
- AutoGeneratingColumnイベント発生時にReactivePropertyの場合はデータバインドにValueを使用し、Valueのデータ型に応じて列作成するように変更
参考ページ
列作成処理
■ ReactiveProperty用の列作成処理を定義
- プロパティがReactivePropertyの時に列を作成して返す
- データバインド時にValueを使用するように変更
- Valueのデータ型に応じて列を作成する
public static class ReactivePropertyUtility { public static bool TryCreateAutoColumn(string propertyName, Type propertyType, out DataGridColumn dataGridColumn) { dataGridColumn = null; // ReactivePropertyでない場合は処理終了 if (!(propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Codeplex.Reactive.ReactiveProperty<>))) return false; // Valueプロパティの情報を取得 var valuePropertyInfo = propertyType.GetProperty("Value"); if (valuePropertyInfo == null) return false; var valuePropertyType = valuePropertyInfo.PropertyType; // データ型に応じて列作成する DataGridComboBoxColumn comboBoxColumn = null; if (valuePropertyType.IsEnum) { comboBoxColumn = new DataGridComboBoxColumn(); comboBoxColumn.ItemsSource = Enum.GetValues(valuePropertyType); dataGridColumn = comboBoxColumn; } else if (typeof(string).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridTextColumn(); } else if (typeof(bool).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridCheckBoxColumn(); } else if (typeof(Uri).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridHyperlinkColumn(); } else { dataGridColumn = new DataGridTextColumn(); } // 並び替え可能か設定 if (!typeof(IComparable).IsAssignableFrom(valuePropertyType)) { dataGridColumn.CanUserSort = false; } // ヘッダーの名称 dataGridColumn.Header = propertyName; // データバインドの設定、更新できない場合はOneWayを設定 DataGridBoundColumn boundColumn = dataGridColumn as DataGridBoundColumn; if (boundColumn != null || comboBoxColumn != null) { Binding binding = new Binding(propertyName + ".Value"); if (comboBoxColumn != null) { comboBoxColumn.SelectedItemBinding = binding; } else { boundColumn.Binding = binding; } if (valuePropertyInfo.CanWrite) binding.Mode = BindingMode.TwoWay; else binding.Mode = BindingMode.OneWay; } return true; } }
■ AutoGeneratingColumnイベント発生時に列を差し替える
// 列自動生成イベント this.dataGrid1.AutoGeneratingColumn += (sender, args) => { DataGridColumn column; // 列情報が作成できた場合に列を差し替える if (ReactivePropertyUtility.TryCreateAutoColumn(args.PropertyName, args.PropertyType, out column)) { // 差し替え args.Column = column; } };
コード全体
- MainViewModel.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.ObjectModel; using System.Reactive.Linq; using Codeplex.Reactive; namespace DataGridSample1 { public class MainViewModel { public MainViewModel() { // テストデータを作成 this.DataGridData = new[] { new Record(false, "Name1", 20, GenderType.Male), new Record(true, "Name2", 30, GenderType.Female) } .ToObservable() .ToReadOnlyReactiveCollection(); } public ReadOnlyReactiveCollection<Record> DataGridData { get; private set; } } // 性別 public enum GenderType { Male, Female } // テストレコード用の定義クラス public class Record { public ReactiveProperty<bool> IsCheck { get; private set; } public ReactiveProperty<string> Name { get; private set; } public ReactiveProperty<int> Age { get; private set; } public ReactiveProperty<GenderType> Gender { get; private set; } public ReactiveProperty<long> AutoCountUp { get; private set; } public Record(bool check, string name, int age, GenderType gender) { this.IsCheck = new ReactiveProperty<bool>(check); this.Name = new ReactiveProperty<string>(name); this.Age = new ReactiveProperty<int>(age); this.Gender = new ReactiveProperty<GenderType>(gender); // 1秒間隔で値を更新 this.AutoCountUp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReactiveProperty(); } } }
- MainWindow.xaml
<Window x:Class="DataGridSample1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:DataGridSample1" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainViewModel></vm:MainViewModel> </Window.DataContext> <Grid> <DataGrid Name="dataGrid1" ItemsSource="{Binding DataGridData}"></DataGrid> </Grid> </Window>
- MainWindow.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace DataGridSample1 { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // 列自動生成イベント this.dataGrid1.AutoGeneratingColumn += (sender, args) => { DataGridColumn column; // 列情報が作成できた場合に列を差し替える if (ReactivePropertyUtility.TryCreateAutoColumn(args.PropertyName, args.PropertyType, out column)) { // 差し替え args.Column = column; } }; } } public static class ReactivePropertyUtility { public static bool TryCreateAutoColumn(string propertyName, Type propertyType, out DataGridColumn dataGridColumn) { dataGridColumn = null; // ReactivePropertyでない場合は処理終了 if (!(propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Codeplex.Reactive.ReactiveProperty<>))) return false; // Valueプロパティの情報を取得 var valuePropertyInfo = propertyType.GetProperty("Value"); if (valuePropertyInfo == null) return false; var valuePropertyType = valuePropertyInfo.PropertyType; // データ型に応じて列作成する DataGridComboBoxColumn comboBoxColumn = null; if (valuePropertyType.IsEnum) { comboBoxColumn = new DataGridComboBoxColumn(); comboBoxColumn.ItemsSource = Enum.GetValues(valuePropertyType); dataGridColumn = comboBoxColumn; } else if (typeof(string).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridTextColumn(); } else if (typeof(bool).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridCheckBoxColumn(); } else if (typeof(Uri).IsAssignableFrom(valuePropertyType)) { dataGridColumn = new DataGridHyperlinkColumn(); } else { dataGridColumn = new DataGridTextColumn(); } // 並び替え可能か設定 if (!typeof(IComparable).IsAssignableFrom(valuePropertyType)) { dataGridColumn.CanUserSort = false; } // ヘッダーの名称 dataGridColumn.Header = propertyName; // データバインドの設定、更新できない場合はOneWayを設定 DataGridBoundColumn boundColumn = dataGridColumn as DataGridBoundColumn; if (boundColumn != null || comboBoxColumn != null) { Binding binding = new Binding(propertyName + ".Value"); if (comboBoxColumn != null) { comboBoxColumn.SelectedItemBinding = binding; } else { boundColumn.Binding = binding; } if (valuePropertyInfo.CanWrite) binding.Mode = BindingMode.TwoWay; else binding.Mode = BindingMode.OneWay; } return true; } } }
ReactivePropertyでDirectoryTreeをバインド
ディレクトリツリーをTreeViewにReactivePropertyを使ってバインドしてみた時のメモ書き。
- 親ノード展開時に子ノードが1階層分の子情報を取得。
- 一度展開されたノードは、FileSystemWatcherクラスで変更監視して変更を反映させる。
メモ
コレクションから削除されたタイミングでのDisposeの呼び方を調べる。
→ ObserveRemoveChangedItemsメソッドでコレクションから削除されたタイミングで処理できるみたい。
参考ページ
- ReactiveProperty オーバービュー
- ReactiveProperty ver 0.3.0.0 - MとVMのバインディングという捉え方
- WPF4.5入門 その25 「TreeViewコントロール その1」
- WPF4.5入門 その26 「TreeViewコントロール その2」
ディレクトリツリー用のViewModel定義
■ DirectoryTreeViewModelのベースを定義
- Viewとのバインド用のプロパティ定義
- 内部処理用のプロパティ定義
public class DirectoryTreeViewModel { /* Viewとのバインド用プロパティ */ // 名称 public ReactiveProperty<string> Text { get; private set; } // 選択フラグ public ReactiveProperty<bool> IsSelected { get; private set; } // 展開フラグ public ReactiveProperty<bool> IsExpanded { get; private set; } // 子要素 public ReadOnlyReactiveCollection<DirectoryTreeViewModel> Children { get; private set; } /* 内部処理用のプロパティ */ // ディレクトリ public DirectoryInfo Directory { get; private set; } // 親ノード public DirectoryTreeViewModel ParentNode { get; private set; } // Dispose用 public CompositeDisposable Disposable { get; private set; } // ルート情報を作成 public DirectoryTreeViewModel() { } // ノード情報を作成 public DirectoryTreeViewModel(DirectoryInfo directory, DirectoryTreeViewModel parent) { } }
■ ルート情報作成の処理
- ルートディレクトリ情報取得用のメソッドを定義
- ルートディレクトリ情報を作成
public static class DirectoryExtensions { /// <summary> /// ルート要素のディレクトリ情報を取得 /// </summary> /// <returns></returns> public static IEnumerable<DirectoryInfo> GetRootDirectories() { // とりあえず固定ドライブのみを取得 return Environment.GetLogicalDrives() .Where(x => new DriveInfo(x).DriveType == DriveType.Fixed) .Select(x => new DirectoryInfo(x)); } } // ルート情報を作成 public DirectoryTreeViewModel() { this.Text = new ReactiveProperty<string>("ルート"); this.IsSelected = new ReactiveProperty<bool>(false); this.IsExpanded = new ReactiveProperty<bool>(false); this.Children = DirectoryExtensions.GetRootDirectories() .Select(x => new DirectoryTreeViewModel(x, this)) .ToObservable() .ToReadOnlyReactiveCollection(); }
■ ノード情報作成の処理
- ノード情報の作成処理
- とりあえず親要素が展開された初回にだけ子要素を取得するようにする。
// ノード情報を作成 public DirectoryTreeViewModel(DirectoryInfo directory, DirectoryTreeViewModel parent) { this.Directory = directory; this.ParentNode = parent; this.Text = new ReactiveProperty<string>(this.Directory.Name); this.IsSelected = new ReactiveProperty<bool>(false); this.IsExpanded = new ReactiveProperty<bool>(false); // 子要素のディレクトリ情報取得用のObservable // アクセス不可の場合は空情報を作成する var childrenObservable = Observable.Defer(() => this.Directory.EnumerateDirectories().ToObservable()) .Catch((UnauthorizedAccessException ex) => Observable.Empty<DirectoryInfo>()); // 親要素の展開フラグがtrueになった初回時に子要素を取得 this.Children = this.ParentNode.IsExpanded.Where(x => x) .DistinctUntilChanged() .SelectMany(x => childrenObservable) .Select(x => new DirectoryTreeViewModel(x, this)) .ToReadOnlyReactiveCollection(); }
■ FileSystemWatcherクラスのイベント変換処理
- FileSystemWatcherのイベントをObservable化するクラスを定義
public static class FileSystemWatcherExtensions { /// <summary> /// ChangedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> ChangedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Changed += h, h => source.Changed -= h); } /// <summary> /// CreatedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> CreatedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Created += h, h => source.Created -= h); } /// <summary> /// DeletedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> DeletedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Deleted += h, h => source.Deleted -= h); } /// <summary> /// RenamedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<RenamedEventArgs> RenamedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<RenamedEventHandler, RenamedEventArgs>( h => (sender, args) => h(args), h => source.Renamed += h, h => source.Renamed -= h); } }
■ ディレクトリツリーの変更通知をするObservableクラスを作成
- ディレクトリツリーの変更通知をするObservableクラス
- FileSystemWatcherの作成、削除、名前変更のイベントを加工、合成して作成
public class FileSystemWatcherObservable { public enum ChangeEventType { Create, Delete }; public string Path { get; private set; } public ChangeEventType EventType { get; private set; } public FileSystemWatcherObservable(string path, ChangeEventType type) { this.Path = path; this.EventType = type; } public static IObservable<FileSystemWatcherObservable> GetObservable(DirectoryInfo directory) { return saveDict[directory.Root.FullName]; } // ディレクトリツリー変更監視用のIObservable保存用ディクショナリ static Dictionary<string, IObservable<FileSystemWatcherObservable>> saveDict = new Dictionary<string, IObservable<FileSystemWatcherObservable>>(); // ルートディレクトリ毎に変更監視用のIObservableを作成 static FileSystemWatcherObservable() { foreach (var item in DirectoryExtensions.GetRootDirectories()) { saveDict.Add(item.Root.FullName, CreateObservable(item)); } } // ディレクトリツリー変更監視用のIObservable作成処理 static IObservable<FileSystemWatcherObservable> CreateObservable(DirectoryInfo directory) { var subject = new Subject<FileSystemWatcherObservable>(); // FileSystemWatcher作成 var watcher = new FileSystemWatcher(); // ディレクトリのルートをパスに設定 watcher.Path = directory.Root.FullName; // ディレクトリの変更情報のみを通知 watcher.NotifyFilter = NotifyFilters.DirectoryName; // サブディレクトリ以下も監視する watcher.IncludeSubdirectories = true; // Createdイベントを加工 var createEvent = watcher.CreatedAsObservable() .Select(x => new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Create)); // Deletedイベントを加工 var deleteEvent = watcher.DeletedAsObservable() .Select(x => new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Delete)); // Renamedイベントを加工 // RenamedイベントはDeleteとCreateの2つに分割する var renameEvent = watcher.RenamedAsObservable() .SelectMany(x => { return new[] { new FileSystemWatcherObservable(x.OldFullPath, ChangeEventType.Delete), new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Create) } .ToObservable(); }) .Publish(); // 加工したFileSystemWatcherをマージして値を発行する Observable .Merge( ThreadPoolScheduler.Instance, createEvent, deleteEvent, renameEvent ) .Subscribe( x => subject.OnNext(x), ex => subject.OnError(ex), () => subject.OnCompleted()); renameEvent.Connect(); watcher.EnableRaisingEvents = true; return subject.AsObservable(); } }
■ ノード情報作成処理にディレクトリツリーの変更通知を追加
- ディレクトリツリーの変更監視をして、展開後のディレクトリ削除、作成時に変更を反映
- 要素削除時にIObservableの購読解除する。(削除された子孫要素以下全て)
// 子孫要素を全て取得 public IEnumerable<DirectoryTreeViewModel> GetAllChildrenEnumerable() { Queue<DirectoryTreeViewModel> queue = new Queue<DirectoryTreeViewModel>(); foreach (var node in this.Children) queue.Enqueue(node); while(queue.Count>0) { var node = queue.Dequeue(); yield return node; foreach (var child in node.Children) queue.Enqueue(child); } } // ノード情報を作成 public DirectoryTreeViewModel(DirectoryInfo directory, DirectoryTreeViewModel parent) { this.Disposable = new CompositeDisposable(); this.Directory = directory; this.ParentNode = parent; this.Text = new ReactiveProperty<string>(this.Directory.Name); this.IsSelected = new ReactiveProperty<bool>(false); this.IsExpanded = new ReactiveProperty<bool>(false); // 子要素のディレクトリ情報取得用のObservable // アクセス不可の場合は空情報を作成する var childrenObservable = Observable.Defer(() => this.Directory.EnumerateDirectories().ToObservable()) .Catch((UnauthorizedAccessException ex) => Observable.Empty<DirectoryInfo>()) .Select((x, i) => CollectionChanged<DirectoryTreeViewModel>.Add(i, new DirectoryTreeViewModel(x, this))); // ディレクトリツリー変更通知を受け取る var watcherObservable = FileSystemWatcherObservable.GetObservable(this.Directory); // 現ノードの直近の子情報のイベントのみを処理する watcherObservable = watcherObservable.Where(x => { string parentPath = new string( x.Path.Reverse() .SkipWhile(y => y != Path.DirectorySeparatorChar) .Skip(1) .Reverse() .ToArray() ); return parentPath == this.Directory.FullName.Trim('\\'); }); // Createdイベントでノード追加のCollectionChangedを作成 var createEvent = watcherObservable.Where(x => x.EventType == FileSystemWatcherObservable.ChangeEventType.Create) // 同一名称が存在しないか確認 .Where(x => { return !this.Children.Any(y => y.Text.Value == Path.GetFileName(x.Path)); }) .Select(x => { var name = Path.GetFileName(x.Path); // 文字列比較して挿入位置のノードを取得 var node = this.Children.FirstOrDefault(y => y.Text.Value.CompareTo(name) > 0); // 追加するindex位置を取得 int index; if (node == null) index = this.Children.Count; else index = this.Children.IndexOf(node); // 要素追加 return CollectionChanged<DirectoryTreeViewModel>.Add(index, new DirectoryTreeViewModel(new DirectoryInfo(x.Path), this)); }); // Deleteイベントでノード削除のCollectionChangedを作成 var deleteEvent = watcherObservable.Where(x => x.EventType == FileSystemWatcherObservable.ChangeEventType.Delete) // 削除対象が存在するか確認 .Where(x => { return this.Children.Any(y => y.Text.Value == Path.GetFileName(x.Path)); }) .Select(x => { var name = Path.GetFileName(x.Path); // 文字列比較して削除位置のノードを取得 var node = this.Children.FirstOrDefault(y => y.Text.Value == name); // 削除するindex位置を取得 int index = this.Children.IndexOf(node); return CollectionChanged<DirectoryTreeViewModel>.Remove(index); }); // 子要素のコレクション情報更新・削除用のObservableを作成 var mergeObservable = Observable .Merge( this.ParentNode.IsExpanded .Where(x => x) .Take(1) .SelectMany(y => Observable.Concat(childrenObservable, createEvent.ObserveOnUIDispatcher())), deleteEvent.ObserveOnUIDispatcher() ) .Publish(); this.Children = mergeObservable.ToReadOnlyReactiveCollection(); mergeObservable.Connect() .AddTo(this.Disposable); // 要素削除された時の後処理 this.Children.ObserveRemoveChangedItems() .Subscribe(x => { foreach(var deleteNode in x) { deleteNode.Disposable.Dispose(); foreach(var child in deleteNode.GetAllChildrenEnumerable().ToArray()) { child.Disposable.Dispose(); } } }) .AddTo(this.Disposable); }
コード全体
- MainWindowViewModel.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using Codeplex.Reactive; using Codeplex.Reactive.Extensions; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Concurrency; using System.Reactive.Disposables; namespace DirectoryTreeViewSample { public class MainWindowViewModel { public MainWindowViewModel() { this.DirectoryTree = new[] { new DirectoryTreeViewModel() }; } public IEnumerable<DirectoryTreeViewModel> DirectoryTree { get; private set; } } public class DirectoryTreeViewModel { /* Viewとのバインド用プロパティ */ // 名称 public ReactiveProperty<string> Text { get; private set; } // 選択フラグ public ReactiveProperty<bool> IsSelected { get; private set; } // 展開フラグ public ReactiveProperty<bool> IsExpanded { get; private set; } // 子要素 public ReadOnlyReactiveCollection<DirectoryTreeViewModel> Children { get; private set; } /* 内部処理用のプロパティ */ // ディレクトリ public DirectoryInfo Directory { get; private set; } // 親ノード public DirectoryTreeViewModel ParentNode { get; private set; } // Dispose用 public CompositeDisposable Disposable { get; private set; } // 子孫要素を全て取得 public IEnumerable<DirectoryTreeViewModel> GetAllChildrenEnumerable() { Queue<DirectoryTreeViewModel> queue = new Queue<DirectoryTreeViewModel>(); foreach (var node in this.Children) queue.Enqueue(node); while(queue.Count>0) { var node = queue.Dequeue(); yield return node; foreach (var child in node.Children) queue.Enqueue(child); } } // ルート情報を作成 public DirectoryTreeViewModel() { this.Text = new ReactiveProperty<string>("ルート"); this.IsSelected = new ReactiveProperty<bool>(false); this.IsExpanded = new ReactiveProperty<bool>(false); this.Children = DirectoryExtensions.GetRootDirectories() .Select(x => new DirectoryTreeViewModel(x, this)) .ToObservable() .ToReadOnlyReactiveCollection(); } // ノード情報を作成 public DirectoryTreeViewModel(DirectoryInfo directory, DirectoryTreeViewModel parent) { this.Disposable = new CompositeDisposable(); this.Directory = directory; this.ParentNode = parent; this.Text = new ReactiveProperty<string>(this.Directory.Name); this.IsSelected = new ReactiveProperty<bool>(false); this.IsExpanded = new ReactiveProperty<bool>(false); // 子要素のディレクトリ情報取得用のObservable // アクセス不可の場合は空情報を作成する var childrenObservable = Observable.Defer(() => this.Directory.EnumerateDirectories().ToObservable()) .Catch((UnauthorizedAccessException ex) => Observable.Empty<DirectoryInfo>()) .Select((x, i) => CollectionChanged<DirectoryTreeViewModel>.Add(i, new DirectoryTreeViewModel(x, this))); // ディレクトリツリー変更通知を受け取る var watcherObservable = FileSystemWatcherObservable.GetObservable(this.Directory); // 現ノードの直近の子情報のイベントのみを処理する watcherObservable = watcherObservable.Where(x => { string parentPath = new string( x.Path.Reverse() .SkipWhile(y => y != Path.DirectorySeparatorChar) .Skip(1) .Reverse() .ToArray() ); return parentPath == this.Directory.FullName.Trim('\\'); }); // Createdイベントでノード追加のCollectionChangedを作成 var createEvent = watcherObservable.Where(x => x.EventType == FileSystemWatcherObservable.ChangeEventType.Create) // 同一名称が存在しないか確認 .Where(x => { return !this.Children.Any(y => y.Text.Value == Path.GetFileName(x.Path)); }) .Select(x => { var name = Path.GetFileName(x.Path); // 文字列比較して挿入位置のノードを取得 var node = this.Children.FirstOrDefault(y => y.Text.Value.CompareTo(name) > 0); // 追加するindex位置を取得 int index; if (node == null) index = this.Children.Count; else index = this.Children.IndexOf(node); // 要素追加 return CollectionChanged<DirectoryTreeViewModel>.Add(index, new DirectoryTreeViewModel(new DirectoryInfo(x.Path), this)); }); // Deleteイベントでノード削除のCollectionChangedを作成 var deleteEvent = watcherObservable.Where(x => x.EventType == FileSystemWatcherObservable.ChangeEventType.Delete) // 削除対象が存在するか確認 .Where(x => { return this.Children.Any(y => y.Text.Value == Path.GetFileName(x.Path)); }) .Select(x => { var name = Path.GetFileName(x.Path); // 文字列比較して削除位置のノードを取得 var node = this.Children.FirstOrDefault(y => y.Text.Value == name); // 削除するindex位置を取得 int index = this.Children.IndexOf(node); return CollectionChanged<DirectoryTreeViewModel>.Remove(index); }); // 子要素のコレクション情報更新・削除用のObservableを作成 var mergeObservable = Observable .Merge( this.ParentNode.IsExpanded .Where(x => x) .Take(1) .SelectMany(y => Observable.Concat(childrenObservable, createEvent.ObserveOnUIDispatcher())), deleteEvent.ObserveOnUIDispatcher() ) .Publish(); this.Children = mergeObservable.ToReadOnlyReactiveCollection(); mergeObservable.Connect() .AddTo(this.Disposable); // 要素削除された時の後処理 this.Children.ObserveRemoveChangedItems() .Subscribe(x => { foreach(var deleteNode in x) { deleteNode.Disposable.Dispose(); foreach(var child in deleteNode.GetAllChildrenEnumerable().ToArray()) { child.Disposable.Dispose(); } } }) .AddTo(this.Disposable); } } public class FileSystemWatcherObservable { public enum ChangeEventType { Create, Delete }; public string Path { get; private set; } public ChangeEventType EventType { get; private set; } public FileSystemWatcherObservable(string path, ChangeEventType type) { this.Path = path; this.EventType = type; } public static IObservable<FileSystemWatcherObservable> GetObservable(DirectoryInfo directory) { return saveDict[directory.Root.FullName]; } // ディレクトリツリー変更監視用のIObservable保存用ディクショナリ static Dictionary<string, IObservable<FileSystemWatcherObservable>> saveDict = new Dictionary<string, IObservable<FileSystemWatcherObservable>>(); // ルートディレクトリ毎に変更監視用のIObservableを作成 static FileSystemWatcherObservable() { foreach (var item in DirectoryExtensions.GetRootDirectories()) { saveDict.Add(item.Root.FullName, CreateObservable(item)); } } // ディレクトリツリー変更監視用のIObservable作成処理 static IObservable<FileSystemWatcherObservable> CreateObservable(DirectoryInfo directory) { var subject = new Subject<FileSystemWatcherObservable>(); // FileSystemWatcher作成 var watcher = new FileSystemWatcher(); // ディレクトリのルートをパスに設定 watcher.Path = directory.Root.FullName; // ディレクトリの変更情報のみを通知 watcher.NotifyFilter = NotifyFilters.DirectoryName; // サブディレクトリ以下も監視する watcher.IncludeSubdirectories = true; // Createdイベントを加工 var createEvent = watcher.CreatedAsObservable() .Select(x => new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Create)); // .Deletedイベントを加工 var deleteEvent = watcher.DeletedAsObservable() .Select(x => new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Delete)); // Renamedイベントを加工 // RenamedイベントはDeleteとCreateの2つに分割する var renameEvent = watcher.RenamedAsObservable() .SelectMany(x => { return new[] { new FileSystemWatcherObservable(x.OldFullPath, ChangeEventType.Delete), new FileSystemWatcherObservable(x.FullPath, ChangeEventType.Create) } .ToObservable(); }) .Publish(); // 加工したFileSystemWatcherをマージして値を発行する Observable .Merge( ThreadPoolScheduler.Instance, createEvent, deleteEvent, renameEvent ) .Subscribe( x => subject.OnNext(x), ex => subject.OnError(ex), () => subject.OnCompleted()); renameEvent.Connect(); watcher.EnableRaisingEvents = true; return subject.AsObservable(); } } public static class FileSystemWatcherExtensions { /// <summary> /// ChangedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> ChangedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Changed += h, h => source.Changed -= h); } /// <summary> /// CreatedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> CreatedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Created += h, h => source.Created -= h); } /// <summary> /// DeletedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<FileSystemEventArgs> DeletedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>( h => (sender, args) => h(args), h => source.Deleted += h, h => source.Deleted -= h); } /// <summary> /// RenamedイベントをObservable化 /// </summary> /// <param name="source"></param> /// <returns></returns> public static IObservable<RenamedEventArgs> RenamedAsObservable(this FileSystemWatcher source) { return Observable.FromEvent<RenamedEventHandler, RenamedEventArgs>( h => (sender, args) => h(args), h => source.Renamed += h, h => source.Renamed -= h); } } public static class DirectoryExtensions { /// <summary> /// ルート要素のディレクトリ情報を取得 /// </summary> /// <returns></returns> public static IEnumerable<DirectoryInfo> GetRootDirectories() { // とりあえず固定ドライブのみを取得 return Environment.GetLogicalDrives() .Where(x => new DriveInfo(x).DriveType == DriveType.Fixed) .Select(x => new DirectoryInfo(x)); } } }
* MainWindow.xaml
<Window x:Class="DirectoryTreeViewSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:DirectoryTreeViewSample" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainWindowViewModel/> </Window.DataContext> <Grid> <TreeView Margin="0" Grid.Column="0" ItemsSource="{Binding DirectoryTree}" > <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsSelected" Value="{Binding Path=IsSelected.Value, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded.Value, Mode=TwoWay}"/> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <StackPanel Orientation="Horizontal" Height="20"> <TextBlock Text="{Binding Text.Value}"/> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
Reactive Extensions 2.2 まとめ(1)
ReactiveExtensions 2.2.4を勉強した事のまとめ(随時更新中)
Taskとの相互変換
IObservableとTaskで相互に変換できる。
詳細な情報は@xin9leさんのブログ(Rxとの相互運用)が分かりやすいと思います。
■IObservableからTaskへの変換
- ToTaskメソッドでTaskに変換。
- OnComplete発行でTaskの処理完了する。
- OnComplete直前のOnNextの値がTask.Resultになる。
var task = Observable.Range(1, 3).ToTask(); // IObservableをTaskに変換 Console.WriteLine(await task); // taskの結果を表示(3が表示される)
■TaskからIObservableへの変換
- ToObservableメソッドでIObservableに変換。
- IObservableの他の非同期系の物と動作が一緒で、処理中はHot、処理完了したらCold(Subscribeする度にキャッシュしているOnComplete直前のOnNextの値を発行)になる。
var task = Task.Run(() => "ToObservable"); // 文字列を返すTask生成 var observable = task.ToObservable(); // TaskをIObservableに変換 observable.Subscribe(x => Console.WriteLine(x));
await
IObservableもawaitする事が出来るようになった。
詳細な情報は下記のサイトが分かりやすいと思います。
@xin9leさんのブログ(Rxとの相互運用)
@neueccさんのブログ(Reactive Extensions v1.1.11011.11リリースに見る.NET 4.5からの非同期処理)
- IObservableに対してawaitすることで非同期で値を取り出す事が出来る。
- 取得出来る値はOnComplete直前のOnNextの値(他の非同期系のIObservableと同じような動き)。
var result = await Observable.Range(1, 3); // awaitで値取り出し Console.WriteLine(result); // 結果表示(3)
Wait
Waitメソッドが追加された。詳細な情報は下記のサイトが分かりやすいと思います。
@neueccさんのブログ(Reactive Extensions v1.1.11011.11リリースに見る.NET 4.5からの非同期処理)
- 同期的に最後の値を取得
- Lastと同じ動作
- 元々あった同期的に値を取り出すメソッド(First等)がObsoleteになり
その代わりとして追加された?
var result = Observable.Range(1, 3).Wait(); // 同期的に最後の値を取得 Console.WriteLine(result); // 結果表示(3)
FirstAsync, FirstOrDefaultAsync, LastAsync, LastOrDefaultAsync, SingleAsync, SingleOrDefaultAsync
同期的に値を取得するメソッドがObsoleteになり
その代わりに非同期で取得出来るメソッドが追加された?
詳細な情報は下記のサイトが分かりやすいと思います。
@neueccさんのブログ(Reactive Extensions v1.1.11011.11リリースに見る.NET 4.5からの非同期処理)
非推奨メソッド | 追加メソッド |
---|---|
First | FirstAsync |
FirstOrDefault | FirstOrDefaultAsync |
Last | LastAsync |
LastOrDefault | LastOrDefaultAsync |
Single | SingleAsync |
SingleOrDefault | SingleOrDefaultAsync |
var ids = Observable.Range(1,3); var result1 = await ids.FirstAsync(); // 非同期で取得(1の値を取得) var result2 = ids.FirstAsync().Wait(); // 同期的に取得(1の値を取得)
FromAsyncPattern
FromAsyncPatternメソッドがObsoleteに変更された。
詳細な情報は下記のサイトが分かりやすいと思います。
@neueccさんのブログ(Reactive Extensions v1.1.11011.11リリースに見る.NET 4.5からの非同期処理)
- .NET4.5から非同期系のメソッド(***Async)が追加されたのが理由?
- TaskFactory.FromAsyncを使いTaskを取得しToObservableで
IObservableに変換する事で非推奨のメソッドを使わずIObservable化は可能
ForEach、ForEachAsync
ForEachメソッドがObsoleteになり新たにForEachAsyncメソッドが追加された。
- ForEachAsyncメソッドを使うことにより非同期にForEachの処理が出来る。
- ForEachメソッドでは、ForEachを呼んだスレッドで同期的に実行されていたので処理が
終了するまでスレッドがブロックされる。 - ForEachAsyncでは引数で渡されたIObservableに対してSubscribeして
OnNextで発行された値に対してactionを実行しているので
IObservableがカレントスレッドで動作している場合はスレッドがブロックされるので
非同期に動作させたい場合はスレッドプール上等で動かす必要がある。
// Rangeがカレントスレッドで動くのでawaitしているのに制御戻らず // ForEachAsyncの処理が終わるまでスレッドブロックされる await Observable.Range(1, 10000000) .ForEachAsync(x => Console.WriteLine(x)); // Rangeがスレッドプールで動くので非同期でForEachAsyncが処理される await Observable.Range(1, 10000000, System.Reactive.Concurrency.ThreadPoolScheduler.Instance) .ForEachAsync(x => Console.WriteLine(x)); // ForEachの場合はRangeをスレッドプールで動作させても // ForEachが同期的に実行されるので呼んだスレッドが処理終了されるまでブロックされる Observable.Range(1, 10000000, System.Reactive.Concurrency.ThreadPoolScheduler.Instance) .ForEach(x => Console.WriteLine(x));
- ForEachAsyncは非同期で処理できるが、引数に渡せるのは
Action型なのでOnNext毎に実行される処理を非同期にしてしまうと正しく完了待機できない
OnNext毎のactionの処理も非同期にしたい場合はSelectManyを使う
(詳細は@neueccさんのブログ(非同期時代のLINQ)参照)。
DelaySubscription
ディレイ処理のメソッドとして、DelaySubscriptionメソッドが追加された。
■Delayメソッドの動作
Delayメソッドは発行された値を後続に流すのを遅延する。
Delay中に発行された値をキャッシュしておき、指定時間経過後に後続に流す。
■DelaySubscriptionメソッドの動作
DelaySubscriptionメソッドは処理の開始を遅延する。
Subscribeしてから処理が開始されるまでの時間を遅延する。
遅延中にObservableから値が発行されていても無視する。
開始時間を遅延させるのでメソッドチェーン内のどの位置においても結果は変わらない。
メソッドチェーン内で何度も呼んだ場合はトータルの時間分処理の開始を遅延する。
■動作確認用のObservable作成
// 1,2を発行するObservable(1秒間隔で発行) var delayTestObservable = Observable.Create<int>(observer => { Console.WriteLine("{0:HH:mm:ss}:OnNext(1)", DateTime.Now); observer.OnNext(1); // 1秒スリープ System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); Console.WriteLine("{0:HH:mm:ss}:OnNext(2)", DateTime.Now); observer.OnNext(2); observer.OnCompleted(); return () => { }; });
■Delayメソッドの動作確認
Delayメソッドより後の処理は、発行された値を渡すのが遅延されている。
Delayメソッドより前の処理は、遅延されず動き続ける。
Console.WriteLine("{0:HH:mm:ss}:StartMethod", DateTime.Now); delayTestObservable // OnNextされたらすぐに処理される .Do(x => Console.WriteLine("{0:HH:mm:ss}:Do1({1})", DateTime.Now, x)) // 5秒遅延させる .Delay(TimeSpan.FromSeconds(5)) // 5秒遅延1回なので、OnNextから5秒後に処理される .Do(x => Console.WriteLine("{0:HH:mm:ss}:Do2({1})", DateTime.Now, x)) // 5秒遅延させる .Delay(TimeSpan.FromSeconds(5)) // 5秒遅延2回なので、OnNextから10秒後に処理される .Subscribe(x => Console.WriteLine("{0:HH:mm:ss}:Subscribe({1})", DateTime.Now, x)); /* 実行結果 10:36:54:StartMethod 10:36:54:OnNext(1) 10:36:54:Do1(1) 10:36:55:OnNext(2) 10:36:55:Do1(2) 10:36:59:Do2(1) 10:37:00:Do2(2) 10:37:04:Subscribe(1) 10:37:05:Subscribe(2) */
■DelaySubscriptionメソッドの動作確認
SubScribeされてから実際に処理が開始されるまでが遅延されている。
Console.WriteLine("{0:HH:mm:ss}:StartMethod", DateTime.Now); delayTestObservable //OnNextされたらすぐに処理される .Do(x => Console.WriteLine("{0:HH:mm:ss}:Do1({1})", DateTime.Now, x)) //処理の開始を5秒遅延させる .DelaySubscription(TimeSpan.FromSeconds(5)) //OnNextされたらすぐに処理される .Do(x => Console.WriteLine("{0:HH:mm:ss}:Do2({1})", DateTime.Now, x)) //処理の開始をを更に5秒遅延させる .DelaySubscription(TimeSpan.FromSeconds(5)) //OnNextされたらすぐに処理される .Subscribe(x => Console.WriteLine("{0:HH:mm:ss}:Subscribe({1})", DateTime.Now, x)); /* 実行結果 10:46:43:StartMethod 10:46:53:OnNext(1) 10:46:53:Do1(1) 10:46:53:Do2(1) 10:46:53:Subscribe(1) 10:46:54:OnNext(2) 10:46:54:Do1(2) 10:46:54:Do2(2) 10:46:54:Subscribe(2) */
■DelayメソッドでDelaySubscriptionメソッドと同じ動きをさせるには?
// 開始処理を遅延させたいObservableを定義 var ids = Observable.Range(1, 3) .Do(x => Console.WriteLine("{0:HH:mm:ss}:Range OnNext({1})", DateTime.Now, x)); // Delayを単純に呼んだ場合 Console.WriteLine("{0:HH:mm:ss}:Start", DateTime.Now); ids.Delay(TimeSpan.FromSeconds(5)) .Subscribe(x => Console.WriteLine("{0:HH:mm:ss}:Subscribe OnNext({1})", DateTime.Now, x)); /* 処理結果:SubscribeのOnNext処理が遅延しているだけでidsの開始処理は遅延されていない 13:00:01:Start 13:00:01:Range OnNext(1) 13:00:01:Range OnNext(2) 13:00:01:Range OnNext(3) 13:00:06:Subscribe OnNext(1) 13:00:06:Subscribe OnNext(2) 13:00:06:Subscribe OnNext(3) */ // Emptyに対してDelayしConcatで開始処理を遅延したいObservableを渡す Console.WriteLine("{0:HH:mm:ss}:Start", DateTime.Now); Observable.Empty<int>() .Delay(TimeSpan.FromSeconds(5)) .Concat(ids) .Subscribe(x => Console.WriteLine("{0:HH:mm:ss}:Subscribe OnNext({1})", DateTime.Now, x)); /* 処理結果:idsの開始処理も遅延されている 13:08:03:Start 13:08:08:Range OnNext(1) 13:08:08:Subscribe OnNext(1) 13:08:08:Range OnNext(2) 13:08:08:Subscribe OnNext(2) 13:08:08:Range OnNext(3) 13:08:08:Subscribe OnNext(3) */
ReactivePropertyのソース見れば
OnErrorRetryメソッドでDelayを使った開始処理の遅延がありますのでそっちを参考にしましょう。
Create
- CreateメソッドにTask(Task、CancellationToken)のオーバーロードが追加された。
- Taskのオーバーロードを使用すると、非同期で、0個以上の値を発行するIObservableを作成できる?
- CreateAsyncメソッドは無くなり、Createメソッドのオーバーロードに置き換わった?
- CancellationTokenを引数に貰えるFuncを使用すると購読解除時(Dispose)に
CancellationTokenSource.Cancelメソッドを呼んでくれる。
// 5秒毎に値を発行するIObservable Observable.Create<int>(async observer => { // 値発行 observer.OnNext(1); // 5秒待機(※非同期なメソッド呼び出し Taskを返すデリゲートなのでawait利用できる) await Task.Delay(TimeSpan.FromSeconds(5)); // 値発行 observer.OnNext(2); // 5秒待機(※非同期なメソッド呼び出し Taskを返すデリゲートなのでawait利用できる) await Task.Delay(TimeSpan.FromSeconds(5)); // 値発行 observer.OnNext(3); // 完了 observer.OnCompleted(); // Disposeされた時に実行されるActionを返す return () => { }; }) .Subscribe(x => Console.WriteLine("{0:HH:mm:ss}:Subscribe OnNext({1})", DateTime.Now, x)); /* 処理結果:5秒毎に値が発行されている 13:53:05:Subscribe OnNext(1) 13:53:10:Subscribe OnNext(2) 13:53:15:Subscribe OnNext(3) */
Defer DeferAsync
■Deferメソッド
- Taskを受け取るオーバーロードが増えた
Func<Task<IObservable<TResult>>>を引数に渡す。 - 非同期にIObservableを生成するメソッドをIObservable化する?
- 内部の処理では、Func<Task<IObservable<TResult>>>をStartAsyncに渡して、IObservable<IObservable<TResult>>を取得し、それをMergeでばらしてIObservable<TResult>に変換している?
// 非同期でIObservable<int>を生成するメソッド private async static Task<IObservable<int>> CreateObservableRangeAsync() { // 5秒待機 await Task.Delay(TimeSpan.FromSeconds(5)); // IObservable<int>を生成 return Observable.Range(1, 3); } // 非同期でIObservable<int>を生成するTaskをIObservable化 // Task<IObservable<int>> => IObservable<IObservable<int>> => IObservable<int> の順番で変換される // StartAsync(ToObservable) => Merge の順番でメソッドが呼ばれている IObservable<int> defer = Observable.Defer(CreateObservableRangeAsync); // Subscribe defer.Subscribe(Console.WriteLine); /* 結果表示 1 2 3 */
■DeferAsyncメソッド
- Deferとの違いはCancellationTokenを引数に貰えるだけ?
StartAsync
- Taskを返すメソッドをIObservable化出来る。
- Func<Task>, Func<Task<TResult>>, Func<CancellationToken,Task>, Func<CancellationToken,Task<TResult>>を引数に渡せる。
- 引数無しのFuncは、TaskをToObservableするのと同じ。
- CancellationTokenを引数に受け取るFuncは、購読解除時(Dispose)に
CancellationTokenSourceのCancelを呼んでくれる。
// Taskを返すメソッドをIObservable化 // TaskをToObservableするのと変わらない Observable.StartAsync(() => { return Task.Run(() => "StartAsync"); }) .Subscribe(Console.WriteLine); /* 結果表示 StartAsync */ // CancellationTokenを引数に貰うオーバーロード // 購読解除した時にCancellationTokenSource.Cancelを呼んでくれる // TaskをToObservableで変換した場合は購読解除してもCancelは呼んでくれないのでTaskの処理は終了するまで動き続けている var dispose = Observable.StartAsync(token => { return Task.Run(async () => { try { // Cancelするまで無限ループ while (true) { // Cancelされたら例外発行 token.ThrowIfCancellationRequested(); // 1秒待機 await Task.Delay(TimeSpan.FromSeconds(1)); } return "StartAsync Cancel"; } catch (OperationCanceledException ex) { // cancelされた時にメッセージ表示 Console.WriteLine(ex.Message); throw; } }); }) .Subscribe(Console.WriteLine); // すぐに購読解除 dispose.Dispose(); /* 結果表示 操作はキャンセルされました。 */
FromAsync
- Taskを返すメソッドをIObservable化出来る。
- StartAsyncをDeferで包んでいる。
- Deferで包んでいるのでSubscribeするまでTaskの処理は開始されない。
Using
- Task関係のオーバーロードが追加されている
- Taskの処理を確実に終了してくれる?
Schedulerの指定
Scheduleの指定の仕方が変わった。
今までは、スレッドプール上で実行したい場合はScheduler.ThreadPool
等、Schedulerクラスで定義されているIScheduleを渡していたがObsoleteに変更された。
■新しいSchedulerの指定に使う定義(一部)
- System.Reactive.Concurrency.DefaultScheduler.Instance
- System.Reactive.Concurrency.CurrentThreadScheduler.Instance
- System.Reactive.Concurrency.ImmediateScheduler.Instances
- System.Reactive.Concurrency.ThreadPoolScheduler.Instance
- System.Reactive.Concurrency.NewThreadScheduler.Default
- System.Reactive.Concurrency.TaskPoolScheduler.Default
// スレッドプール上で実行 // 非推奨となったスケジューラの指定方法 Observable.Range(1, 10000, System.Reactive.Concurrency.Scheduler.ThreadPool) .Subscribe(Console.WriteLine); // スレッドプール上で実行 // 今後推奨されるスケジューラの指定方法 Observable.Range(1, 10000, System.Reactive.Concurrency.ThreadPoolScheduler.Instance) .Subscribe(Console.WriteLine);
合成処理
合成のメソッドでTaskを渡せるようになった。
Task<TSource>を受け取るメソッド(SelectMany)
IObservable<Task<TSource>>を受け取るメソッド(Merge等)
- Task<TSource>を受け取るメソッドは内部でTask<TSource>をToObservableしている。
- IObservable<Task<TSource>>を受け取るメソッドは内部でSelect呼んでToObservableしている。
Rx勉強するのに参考にしているサイト
個人的なメモ
- Visual Studio のデバッグ設定で「マイコードのみを有効にする」のチェックをはずしていた方がよい?
これ有効にしてるとOnErrorの処理等記述していてもマイコードで例外をキャッチしていないので例外発生するたびにデバッガが捕捉するので面倒。
mono 3.4.0 をソースコードからインストール(debian7)
- mono3.4.0をdebian7にインストール。
- インストール先は/opt/mono/3.4.0/にインストールした。
- rootユーザーで作業を行う。
必要なパッケージをインストール
■開発ツールをインストール
aptitude install make gcc g++ automake libtool
■libgdiplusのビルドに必要なパッケージをインストール
aptitude install pkg-config libglib2.0-dev libjpeg8-dev libpng12-dev libtiff4-dev libgif-dev libx11-dev libfreetype6-dev libfontconfig1-dev libxrender-dev libcairo2-dev libexif-dev
■ mono 3.4.0のビルドに必要なパッケージをインストール
aptitude install bison gettext
ソースコード保存用のフォルダを作成
mkdir -p /opt/src/mono/
libgdiplus をインストール
■ ソースコード保存用のフォルダに移動しlibgdiplusのソースコードを取得
cd /opt/src/mono/ wget http://download.mono-project.com/sources/libgdiplus/libgdiplus-2.10.9.tar.bz2
■ ソースコードを解凍して解凍ソースの中に移動
tar -jxvf libgdiplus-2.10.9.tar.bz2 cd libgdiplus-2.10.9/
■ インストール先を指定してコンパイル、インストール
./configure --prefix=/opt/mono/3.4.0/ make make install
mono 3.4.0 をインストール
■ ソースコード保存用のフォルダに移動しmono3.4.0のソースコードを取得
cd /opt/src/mono/ wget http://download.mono-project.com/sources/mono/mono-3.4.0.tar.bz2
■ ソースコードを解凍して解凍ソースの中に移動
tar -jxvf mono-3.4.0.tar.bz2 cd mono-3.4.0/
■ インストール先を指定してコンパイル
./autogen.sh --prefix=/opt/mono/3.4.0 --with-libgdiplus=installed make
■ インストール前にファイルを追加する(mono3.4.0ではmake installに必要なtargetファイルが1つ漏れているらしいので手動で追加する)
echo -e \ "<Project xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">" \ "\n\t<Import Project=\"..\Microsoft.Portable.Core.props\" />" \ "\n\t<Import Project=\"..\Microsoft.Portable.Core.targets\" />" \ "\n</Project>" > \ /opt/src/mono/mono-3.4.0/mcs/tools/xbuild/targets/Microsoft.Portable.Common.targets
■ mono3.4.0をインストール
make install
パスの設定
■ インストールしたmonoフォルダをパスに追加する、monoを使用するユーザーの.bashrcに追記
export PATH=/opt/mono/3.4.0/bin:$PATH
■ .bashrc反映
source ~/.bashrc
■ mono のバージョン確認で今回インストールしたバージョンが表示されるか確認
mono --version Mono JIT compiler version 3.4.0 (tarball 2014年 4月 15日 火曜日 17:55:32 JST) Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com TLS: __thread SIGSEGV: altstack Notifications: epoll Architecture: amd64 Disabled: none Misc: softdebug LLVM: supported, not enabled. GC: sgen
EXEファイルの実行方法
■ VisualStudioで作ったexeファイルを持ってきてmonoコマンドでexeファイルを指定する
mono ConsoleApplication1.exe
C# LINQ to Objects (1)
LINQ to Objectsに関して勉強した部分のメモ1回目
基本的な部分のまとめ
- Linq to Objectsとは
- 遅延実行
- 実体化
Linq to Objectsとは
IEnumeable<T>インターフェースを実装しているクラスに対して統一された操作(拡張メソッド)を提供するもの?
IEnumeable<T>インターフェースが実装されていれば、実体をあまり気にすることなくLinq to Objectsが提供する拡張メソッドを利用しデータに対しての処理を行う事が出来る。
Linq to Objectsが提供する拡張メソッド(一部のみ)
■ シーケンスの射影、要素の型と値を加工(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Select | 要素の型と値を加工する |
SelectMany | 2つのシーケンスの全ての組み合わせを行い、要素の型と値を加工する。 |
■ シーケンスのフィルタリング、要素絞込み(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Where | 条件を満たす要素を全て取得(フィルター処理) |
Take | 先頭から指定個数の要素を取得 |
TakeWhile | 先頭から指定条件を満たす限り取得(途中で条件を満たさない要素があった場合はその時点で打ち切り) |
Skip | 先頭から指定個数の要素をスキップ |
SkipWhile | 先頭から指定条件を満たす限りスキップ(途中で条件を満たさない要素があった場合はその時点でスキップ終了) |
■ シーケンスの結合(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Join | 一致するキーに基づいて2つのシーケンスを結合する |
Zip | 2つのシーケンスのマージを行う |
■ シーケンスのグループ化(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
GroupBy | 指定したキーでシーケンスのグループ化 |
GroupJoin | 一致するキーに基づいて2つのシーケンスを結合した結果を指定したキーでグループ化 |
■ シーケンスから重複要素の除去(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Distinct | 1つのシーケンスから重複要素を除去し1つだけにする |
■ シーケンスの結合(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Concat | 2つのシーケンスを結合する |
■ シーケンスの集合演算(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Union | 2つのシーケンスの和集合を行う(重複する要素は除く) |
Except | 2つのシーケンスの差集合を行う |
Intersect | 2つのシーケンスの積集合を行う |
■ シーケンスの並び替え(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Reverse | シーケンスの要素の順番を逆転させる |
OrderBy | シーケンスの要素を昇順で並び替える |
ThenBy | OrderByの結果を維持したままシーケンスの要素を昇順で並び替える |
OrderByDescending | シーケンスの要素を降順で並び替える |
ThenByDescending | OrderByDescendingの結果を維持したままシーケンスの要素を降順で並び替える |
■ キャスト(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Cast | シーケンスの要素を指定した型にキャストする(キャストできない場合は例外発生) |
OfType | シーケンスの要素を指定した型にキャストする(キャストできない要素はシーケンスから除去する) |
■ シーケンス作成(遅延実行:実体化されない)
メソッド名 | 処理内容 |
---|---|
Range | 指定値から1ずつ昇順で指定回数繰り返すシーケンスを作成する |
Repeat | 指定値を指定回数繰り返すシーケンスを作成する |
■ 要素取得(即時実行:実体化される)
メソッド名 | 処理内容 |
---|---|
First | 条件を満たす先頭要素取得(取得できない場合は例外発生) |
FirstOrDefault | 条件を満たす先頭要素取得(取得できない場合は例外発生しない) |
Last | 条件を満たす最終要素取得(取得できない場合は例外発生) |
LastOrDefault | 条件を満たす最終要素取得(取得できない場合は例外発生しない) |
ElementAt | 指定Index位置の要素取得(取得できない場合は例外発生) |
ElementAtOrDefault | 指定Index位置の要素取得(取得できない場合は例外発生しない) |
■ シーケンスの集計(即時実行:実体化される)
メソッド名 | 処理内容 |
---|---|
Count | シーケンスの要素数を取得 |
Max | シーケンスの最大値を取得 |
Min | シーケンスの最小値を取得 |
Sum | シーケンスの合計値を取得 |
■ シーケンスが条件を満たすか判定(即時実行:実体化される)
メソッド名 | 処理内容 |
---|---|
Any | シーケンスの要素が一つでも条件を満たすか判定する(1つでも条件を満たせば処理を即終了、trueを返す) |
All | シーケンスの要素全てが条件を満たすか判定する |
■ シーケンスからコレクションクラスへの変換(即時実行:実体化される)
メソッド名 | 処理内容 |
---|---|
ToArray | シーケンスから配列を作成する |
ToList | シーケンスからListを作成する |
ToDictionary | シーケンスからDictionaryを作成する |
ToLookup | シーケンスからILookupを作成する(1つのキーに対して複数の値を持てる読み取り専用のDictionaryのような物?) |
遅延実行
- Linq to Objectsの提供する拡張メソッドは遅延実行される物と即時実行される物がある。
- 遅延実行される物は実際の処理は実体化されるまで行われない。
実体化
■ 実体化されるタイミング
- 即時実行されるメソッドを呼ぶ(メソッド呼ばれたタイミングで要素毎にクエリが実行される)
- foreachが実行される(ループ回る度に要素毎に定義したクエリが実行される)
// 1~10万までの値を表すシーケンス var datas = Enumerable.Range(1, 100000); // 先頭100要素だけ欲しいのでTakeメソッドでフィルタリングする var takeDatas = datas.Take(100); // この時点ではまだ処理実行されておらず、クエリを定義しただけの状態、 // 実体化された際にシーケンスの要素毎にクエリが実行される、今回はTakeなので100要素取り出した時点で処理が終了するが // Whereだと全要素チェックするので全要素分定義したクエリが実行される // 実体化1 即時実行されるメソッドを呼ぶ var ArrayDatas = takeDatas.ToArray(); // ここで実際に定義したクエリが実行され、シーケンスから先頭100要素を取得する処理が行われ、配列化される // 実体化2 foreachを呼ぶ foreach(var item in takeDatas) { ・・・ // ループが回るたびに、要素毎に定義したクエリが実行される。 }
■ 初回の実体化は重い
- 実体化されていないシーケンスの場合、初回の実体化処理は結構重い
- 実体化されていないシーケンスに対してElementAtを呼んでIndex毎にアクセス等すると毎回実体化の処理が走ってすごく重いので、そういう場合は一度実体化を行い、実体化したシーケンスに対して処理を行う