泥庭

2014年12月8日

dynamicに頼らざるを得ない?

Filed under: .NET — タグ: — yone64 @ 11:12 PM

タイトルわかりにくいですね。

C#のGenericにはWildCardがないので、dynamicに頼らざるを得ないパターンがあると思ってます。
例えば、下記パターン

引数で受け取った、IEnumerableがObservableCollection<T>の場合に、Moveメソッドを呼びたい

「そもそも、非GenericなIEnumerableなんて使うなよ。」って話もなくはないのですが、UI層の各コントロールのプロパティーはItemsControl.ItemsSourceをはじめ、軒並み非Genericなので仕方がないのです。しかも、Moveメソッドの引数・戻値はTに非依存なので、Tが静的に決まらなくても、書きたいですよね。

例えば、↓みたいにかけると、とっても嬉しいのです。

// T を指定しないともちろんコンパイルエラー
if (s is ObservableCollection<>)
{
    ((ObservableCollection<>)s).Move(1,2);
}

でも、無理なものは無理なので、あきらめます。GenericのWildCardはいろいろ難しいらしいし。
で、Typeに問い合わせることになります。ObservableCollectionを継承していることを確認するのが面倒ですが、dynamicが使えると割と短く書けます。

private void Move(IEnumerable s, int oldIndex, int newIndex)
{
    var flag = this.IsGenericSubtype(s.GetType(), typeof(ObservableCollection<>));
  if (flag)
    {
        dynamic d = s;
        d.Move(oldIndex, newIndex);
    }
}

private bool IsGenericSubtype(Type targetType, Type baseType)
{
    while (targetType != null)
    {
        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == baseType)
        {
            return true;
        }
        targetType = targetType.BaseType;
    }
    return false;
}

これをdynamicを使わずに書いてみようと頑張ると、

  1. GenericなWrapperと非GenericなInterfaceを用意して、
  2. 動的にクラスを生成して
  3. 後は、非GenericなInterfaceを通じてアクセス

という手順になるのではないかと。Wrapperをどこかにキャッシュしておいて、後々使いまわすという用途があれば、こちらのほうが有利になるパターンもあるかもしれません。

public interface IObservableCollectioWrapper
{
    void Move(int oldIndex, int newIndex);
}

public class ObservalbeCollectionWrapper<T> : IObservableCollectioWrapper
{
    public ObservableCollection<T> Source { get; private set; }

    public ObservalbeCollectionWrapper(ObservableCollection<T> source)
    {
        this.Source = source;
    }

    public void Move(int oldIndex, int newIndex)
    {
        Source.Move(oldIndex, newIndex);
    }
}

private void Move(IEnumerable s, int oldIndex, int newIndex)
{
    Type[] paramType;
    var flag = this.IsGenericSubtype(s.GetType(), typeof(ObservableCollection<>), out paramType);
    if (flag)
    {
        var wrapper =
            Activator.CreateInstance(typeof(ObservalbeCollectionWrapper<>).MakeGenericType(paramType), s) as
                IObservableCollectioWrapper;
        wrapper.Move(oldIndex, newIndex);
    }
}

private bool IsGenericSubtype(Type targetType, Type baseType, out Type[] paramType)
{
    paramType = null;
    while (targetType != null)
    {
        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == baseType)
        {
            paramType = targetType.GetGenericArguments();
            return true;
        }
        targetType = targetType.BaseType;
    }
    return false;
}

昔(C#3.0時代)は、Reflectionの回数を減らすために、こんなコードを多用してたけど、いまならdynamicでいいよね?
ちなみに、自分でクラス設計する場合は、必ず非ジェネリックなIFを用意するように気をつけています。このパターンも、ObservableCollectionがIObservableCollection(ないけど)を実装していればこんな面倒なことには…

広告

コメントする »

まだコメントはありません。

RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com Blog.

%d人のブロガーが「いいね」をつけました。