Advent Calendar 2012 Winter (C#) これはC# Advent Calender 2012参加記事です。 今年の2月〜10月の間、Windows8の名前を発してはいけないアプリを開発していて、その間にいくつか小道具を作ってはgistに放り込んだので、使えるもの使えないものひっくるめて、大掃除がてら、いくつか紹介いたします。 gist: 2418469 A helper class to extract property-names of ViewModel for MVVM. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Samples { [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class PropertyCategoryAttribute : System.Attribute { public PropertyCategoryAttribute(Type inTagType) { this.Tag = inTagType; } public Type Tag { get; internal set; } } public class PropertyCategoryMapper { public static PropertyCategoryMapper Create<TViewModel>() { return new PropertyCategoryMapper(typeof(TViewModel)); } public static PropertyCategoryMapper Create(Type inType) { return new PropertyCategoryMapper(inType); } private Dictionary<Type, string[]> mTagMap; private PropertyCategoryMapper(Type inType) { mTagMap = ( inType.GetTypeInfo().DeclaredProperties .Where(p => p.CanRead) .SelectMany(p => { return p.GetCustomAttributes<PropertyCategoryAttribute>(true).Select(attr => { return new KeyValuePair<Type, string>(attr.Tag, p.Name); }); }) .GroupBy(pair => pair.Key, pair => pair.Value) .ToDictionary(pair => pair.Key, pair => pair.ToArray()) ); } public string[] ToProprtyNames(Type inTag) { return ToProprtyNamesCore(inTag); } public string[] ToProprtyNames<TCategoryTag>() { return ToProprtyNamesCore(typeof(TCategoryTag)); } private string[] ToProprtyNamesCore(Type inTag) { string[] props; if (mTagMap.TryGetValue(inTag, out props)) { return props; } else { return new string[0]; } } } } WPFでMVVM的なことをやってて、いつも気になるのが INotifyPropertyChanged.PropertyChanged イベントを介して通知を行う際の引数。 あの文字列で渡すってのが、もう気に入らないです(個人的感想です)。 カターンゼンじゃないし…(個人的感想です)。 C# 5.0からは、CallerInfoAttributeが追加されて、幾分楽にはなってるっぽいですが、read-onlyなプロパティにも値をセットしなきゃならんとかチョーメンドイわけです(個人的感想です)。 文字列は自由に表現が出来る分、記述間違いにかどうかは実行時でないと分からない問題を持っていますので、できるだけコンパイル時に蹴ってもらえるよう、専用の型を与えたいと思っていました。 そこで、上のようなコード書いてみました。 使い方は以下のように。。。 namespace TestTag { public interface Unknown {} public interface Numeric {} public interface Strings {} public interface Dates {} public interface Custom1 {} public interface Custom2 {} } internal class PropertyCategoryTestViewModel : INotifyPropertyChanged { private PropertyCategoryMapper mMapper; private DateTime mCreatedAt = DateTime.Now; private DateTime mModifiedAt = DateTime.Now; public PropertyCategoryTestViewModel() { mMapper = PropertyCategoryMapper.Create(this.GetType()); } public event PropertyChanged; protected void OnPropertyChanged(string[] inNames) { if (this.PropertyChanged != null) { foreach (var name in inNames) { this.PropertyChanged(name); } } } public void DoHoge() { // Do something ... this.OnPropertyChanged(mMapper.ToProprtyNames<TestTag.Numeric>()); } [PropertyCategory(typeof(TestTag.Numeric))] public int Value1 { get; set; } [PropertyCategory(typeof(TestTag.Numeric))] [PropertyCategory(typeof(TestTag.Custom1))] public int Value2 { get; set; } [PropertyCategory(typeof(TestTag.Numeric))] [PropertyCategory(typeof(TestTag.Custom2))] public double Num1 { get; set; } [PropertyCategory(typeof(TestTag.Strings))] [PropertyCategory(typeof(TestTag.Custom2))] public string Text1 { get; set; } [PropertyCategory(typeof(TestTag.Dates))] [PropertyCategory(typeof(TestTag.Custom1))] [PropertyCategory(typeof(TestTag.Custom2))] public DateTime CreatedAt { get { return mCreatedAt; } } [PropertyCategory(typeof(TestTag.Dates))] public DateTime ModifiedAt { get { return mModifiedAt; } } } } プロパティにPropertyCategoryAttribute(Type)を付与します。 コンストラクタで、PropertyCategoryMapperをインスタンス化します。 View Modelの各処理の通知個所で、ToProprtyNames<SomeType>()を呼ぶことで、すべての対象のプロパティ名が取得できます。 複数のPropertyCategory属性を付けておけば、タグごとに通知するプロパティを切り替えることが出来るようになります。 参考までに、このgistには、テストコードも付けてます。 本当はPropertyCategory属性の引数に列挙値(enum)を渡したかったのですが、Attributeの仕様で定数、Typeまたはそれらの配列しか認められていなかったため、代わりにinterfaceを使用しました。 それと1回だけとはいえ、リフレクションに頼ってしまったのも辛いところです。しかし、世界中の誰かがきっと、ろずりーんでコンパイル時に生成するコードを書いてくれると信じています。私は、にわかなのでムリです。 以下、疲れたので簡単に。。。 3521721 Any filter extension using System; using System.Collections.Generic; using System.Linq; namespace Sample { public static class EnumerableAnyExtensions { public static IEnumerable<TSource> WhereAny<TSource>(this IEnumerable<TSource> inSelf, params Func<TSource, bool>[] inFilters) { return inSelf.Where( source => inFilters.Any(fn => fn(source)) ); } public static int CountAny<TSource>(this IEnumerable<TSource> inSelf, params Func<TSource, bool>[] inFilters) { return inSelf.Count( source => inFilters.Any(fn => fn(source)) ); } } } 何となく思いついた、Enumerable.WhereでOr (||) を指定するのと同じものです。 Orでもいいんだけど、どこで改行したらいいか分からなくなってイヤーンだし、個々の述語関数使い回したかったし、という理由で作っちゃいました。ホントただの俺得です。 つい最近気づきましたが、Enumerable.Whereって、要素とインデックスが渡されるオーバーロードがあったんですね。 なので作ってはないんですの。 もう一つ、俺得クラスとして、コレクションのfull outer join書いたけど、gistにあげ忘れてたから省略。てへへ。 3046512 Maybeモナドの写経 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Common.Test { public class Maybe<TValue> { public static readonly Maybe<TValue> Nothing = new Maybe<TValue>(); public Maybe(TValue inValue) { this.Value = inValue; } private Maybe() { } public TValue Value { get; private set; } } public static class Maybe { public static Maybe<TValue> ToMaybe<TValue>(this TValue inValue) { return new Maybe<TValue>(inValue); } public static Maybe<TValue> ToMaybe<TValue>(Func<TValue> inFunc) { return inFunc().ToMaybe(); } public static async Task<Maybe<TValue>> ToMaybe<TValue>(Func<Task<TValue>> inFunc) { var value = await inFunc(); return value.ToMaybe(); } } public static class MaybeExtensons { public static Maybe<TValue> NothingUnless<TValue>(this Maybe<TValue> inValue, Func<TValue, bool> inAcceptedFunc) { return inAcceptedFunc(inValue.Value) ? inValue : Maybe<TValue>.Nothing; } public static Maybe<TValue> NothingIfNull<TValue>(this Maybe<TValue> inValue) { return inValue.NothingUnless((v) => v != null); } public static Maybe<TResult> Select<TSource, TResult>(this Maybe<TSource> inSource, Func<TSource, TResult> inResultSelectorFunc) { return SelectCore(inSource, s => inResultSelectorFunc(s)); } public static Maybe<TResult> SelectMany<TSource, TResult>( this Maybe<TSource> inSource, Func<TSource, Maybe<TResult>> inSelectorFunc) { return SelectMany(inSource, inSelectorFunc, (s, t) => t); } public static Maybe<TResult> SelectMany<TSource, TTemporary, TResult>( this Maybe<TSource> inSource, Func<TSource, Maybe<TTemporary>> inSelectorFunc, Func<TSource, TTemporary, TResult> inResultSelector) { return Maybe<TSource>.Nothing != inSource ? Select(inSource, s => inSelectorFunc(s).Select(t => inResultSelector(s, t))).Value : Maybe<TResult>.Nothing; } private static Maybe<TResult> SelectCore<TSource, TResult>(Maybe<TSource> inSource, Func<TSource, TResult> inSelectorFunc) { if (Maybe<TSource>.Nothing == inSource) { return Maybe<TResult>.Nothing; } else { return inSelectorFunc(inSource.Value).ToMaybe(); } } } public static class MaybeAsyncExtensons { public static async Task<Maybe<TValue>> NothingUnlessAsync<TValue>(this Maybe<TValue> inValue, Func<TValue, Task<bool>> inAcceptedFunc) { return await inAcceptedFunc(inValue.Value) ? inValue : Maybe<TValue>.Nothing; } public static async Task<Maybe<TValue>> NothingUnlessAsync<TValue>(this Task<Maybe<TValue>> inValue, Func<TValue, Task<bool>> inAcceptedFunc) { return await NothingUnlessAsync(await inValue, inAcceptedFunc); } public static async Task<Maybe<TValue>> NothingUnlessAsync<TValue>(this Task<Maybe<TValue>> inValue, Func<TValue, bool> inAcceptedFunc) { return MaybeExtensons.NothingUnless(await inValue, inAcceptedFunc); } public static async Task<Maybe<TValue>> NothingIfNullAsync<TValue>(this Task<Maybe<TValue>> inValue) { return MaybeExtensons.NothingUnless(await inValue, (v) => v != null); } public static async Task<Maybe<TResult>> Select<TSource, TResult>(this Maybe<TSource> inSource, Func<TSource, Task<TResult>> inResultSelectorFunc) { var result = MaybeExtensons.Select(inSource, s => inResultSelectorFunc(s)); return Maybe<Task<TResult>>.Nothing != result ? (await result.Value).ToMaybe() : Maybe<TResult>.Nothing; } public static async Task<Maybe<TResult>> Select<TSource, TResult>(this Task<Maybe<TSource>> inSource, Func<TSource, Task<TResult>> inResultSelectorFunc) { return await Select(await inSource, inResultSelectorFunc); } public static async Task<Maybe<TResult>> Select<TSource, TResult>(this Task<Maybe<TSource>> inSource, Func<TSource, TResult> inResultSelectorFunc) { return MaybeExtensons.Select(await inSource, s => inResultSelectorFunc(s)); } public static async Task<Maybe<TResult>> SelectMany<TSource, TResult>(this Task<Maybe<TSource>> inSource, Func<TSource, Task<Maybe<TResult>>> inSelectorFunc) { return await SelectManyCore( await inSource, (s) => inSelectorFunc(s).ToMaybe(), (s, t) => t ); } public static async Task<Maybe<TResult>> SelectMany<TSource, TTemporary, TResult>( this Task<Maybe<TSource>> inSource, Func<TSource, Task<Maybe<TTemporary>>> inSelectorFunc, Func<TSource, TTemporary, TResult> inResultSelector) { return await SelectManyCore( await inSource, (s) => inSelectorFunc(s).ToMaybe(), inResultSelector ); } public static async Task<Maybe<TResult>> SelectMany<TSource, TResult>(this Maybe<TSource> inSource, Func<TSource, Task<Maybe<TResult>>> inSelectorFunc) { return await SelectManyCore( inSource, (s) => inSelectorFunc(s).ToMaybe(), (s, t) => t ); } private static async Task<Maybe<TResult>> SelectManyCore<TSource, TTemporary, TResult>( Maybe<TSource> inSource, Func<TSource, Maybe<Task<Maybe<TTemporary>>>> inSelectorFunc, Func<TSource, TTemporary, TResult> inResultSelector) { var result = MaybeExtensons.SelectMany( inSource, inSelectorFunc, async (s, t) => inResultSelector(s, (await t).Value) ); return Maybe<Task<TResult>>.Nothing != result ? (await (result.Value)).ToMaybe() : Maybe<TResult>.Nothing; } } } 見ての通り、matarillo.com / モナドの驚異の写経です。 ただ、ちょっとだけ手を入れて、c# 5.0のasync / awaitにも対応させてみました。 モナドとかよく分からないですので、写経元見てもらった方がいいと思います。 以上、淡々と並べてみました。 advent, c#