◆ 弱参照な Dictionary に入れる
◆ WPF なら添付プロパティが使える 

拡張メソッド

C# には拡張メソッドという機能があります
元からあるクラスにこれを使ってメソッドを追加できます

JavaScript 風な substr と forEach つくって見ると
static class MethodExtensions
{
    public static string substr(this string self, int start, int length)
    {
        var self_length = self.Length;
        var actual_start = start < 0
            ? Math.Max(0, self_length + start)
            : Math.Min(self_length, start);
        var actual_length = Math.Max(0, Math.Min(length, self_length - actual_start));

        return self.Substring(actual_start, actual_length);
    }

    public static string substr(this string self, int start)
    {
        return self.substr(start, self.Length);
    }

    static public void forEach<T>(this IEnumerable<T> self, Action<T, int, IEnumerable<T>> action)
    {
        var i = 0;
        var list = self.ToList();
        foreach (var val in list)
        {
            action(val, i++, list);
        }
    }
}

一つ目の引数に this キーワードをつけると その型のメソッドとして呼び出せます
var last = "abcd".substr(-1);
Console.WriteLine(last);
// d

new List<int> { 1, 2, 3 }.forEach((e, i, s) => Console.Write(e));
// 123

すごく便利です

メソッドを追加できるならプロパティは?
と思うわけですが ないみたいです

WeakReference

ないなら作りましょう!

プロパティのようなアクセス方法にはできませんがオブジェクトをキーにしたディクショナリを作り そこに好きなデータを入れていきます
キーには弱参照を使って 元が GC で消えるときに一緒にきえるようにします

JavaScript だと WeakMap で DOM の要素に対するデータを保存したりしますがそれと同じようなことをします
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace liblib
{
    public static class ExProp
    {
        private static ConditionalWeakTable<object, Map<string, object>> values { get; }
            = new ConditionalWeakTable<object, Map<string, object>>();

        public static object getExProp(this object self, string prop_name)
        {
            return values.GetOrCreateValue(self)[prop_name];
        }

        public static void setExProp(this object self, string prop_name, object value)
        {
            values.GetOrCreateValue(self)[prop_name] = value;
        }
    }

    public class Map<TKey, TValue> : Dictionary<TKey, TValue>
    {
        public new TValue this[TKey key]
        {
            get
            {
                TValue value;
                if (this.TryGetValue(key, out value))
                {
                    return value;
                }
                else
                {
                    return default(TValue);
                }
            }
            set
            {
                base[key] = value;
            }
        }
    }
}

C# で弱参照してくれるディクショナリは ConditionalWeakTable というクラスです
やりたいことはプロパティ追加なので 任意のオブジェクトに キー(string)バリュー(なんでも保存できるように object)のペアを保存できるようにします

通常の Dictionary では存在しないキーにアクセスすると例外が起きて扱いにくいので 存在しないならデフォルト値を返す Dictionary を Map というクラス名で定義してます

追加プロパティ部分は ExProp クラスだけなのでシンプルです

使い方

class A {}

var a = new A();
a.setExProp("name", "a");
Console.WriteLine(a.getExProp("name"));
// a

var a2 = new A();
Console.WriteLine(a2.getExProp("name") ?? "NULL");
// NULL

拡張メソッド経由で読み書きできます

WPF

話は変わって WPF です

Tag

WPF のコントロールに好きなデータを持たせておいてその値によってスタイルを変えたいことがあります

WPF のコントロールには好きな値を入れておける Tag というプロパティがあります

こんな感じでラベルを作って
public Window1()
{
    this.panel.Children.Add(
        new Label { Content = "red", Tag = "red" });
    this.panel.Children.Add(
        new Label { Content = "blue", Tag = "blue" });
    this.panel.Children.Add(
        new Label { Content = "green", Tag = "green" });
}

トリガで Tag プロパティを使うようにしておけば色を変えられます
<StackPanel x:Name="panel">
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Style.Triggers>
                <Trigger Property="Tag" Value="red">
                    <Setter Property="Foreground" Value="#f44"/>
                </Trigger>
                <Trigger Property="Tag" Value="blue">
                    <Setter Property="Foreground" Value="#38f"/>
                </Trigger>
                <Trigger Property="Tag" Value="green">
                    <Setter Property="Foreground" Value="#8e4"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
</StackPanel>


でも これだと Tag にひとつしかデータを入れておけません
複数入れたいことってよくあるものです

単純に思いつくのは Tag を Dictionary にしていろいろ入れる方法
public Window1()
{
    this.panel.Children.Add(
        new Label { Content = "red", Tag = new Dictionary<string, string> { { "color", "red" } } });
    this.panel.Children.Add(
        new Label { Content = "blue", Tag = new Dictionary<string, string> { { "color", "blue" } } });
    this.panel.Children.Add(
        new Label { Content = "green", Tag = new Dictionary<string, string> { { "color", "green" } } });
}
<StackPanel x:Name="panel">
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Style.Triggers>
                <Trigger Property="Tag[color]" Value="red">
                    <Setter Property="Foreground" Value="#f44"/>
                </Trigger>
                <Trigger Property="Tag[color]" Value="blue">
                    <Setter Property="Foreground" Value="#38f"/>
                </Trigger>
                <Trigger Property="Tag[color]" Value="green">
                    <Setter Property="Foreground" Value="#8e4"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
</StackPanel>

ですが このコードは動かないです

Property に指定するのは DepenencyProperty じゃないといけないようで Tag[color] は認識しないといわれてしまいます

Converter

ちょっと面倒ですが DataTrigger と Converter をつかって Dictionary の値を見れるようにすることができます

Converter は Dictionary から parameter で渡されたキーの値を返すように作ります
class DicReader : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var dic = value as Dictionary<string, string>;

        if (dic == null)
        {
            return null;
        }

        return dic[(string)parameter];
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML のほうでは Binding に要素の Tag プロパティ 上の Converter を設定します
ConverterParameter に Dictionary のキーを入れます
<StackPanel x:Name="panel">
    <StackPanel.Resources>
        <local:DicReader x:Key="dic_reader"/>
        <Style TargetType="Label">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self},Converter={StaticResource dic_reader},ConverterParameter=color}" Value="red">
                    <Setter Property="Foreground" Value="#f44"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self},Converter={StaticResource dic_reader},ConverterParameter=color}" Value="blue">
                    <Setter Property="Foreground" Value="#38f"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self},Converter={StaticResource dic_reader},ConverterParameter=color}" Value="green">
                    <Setter Property="Foreground" Value="#8e4"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
</StackPanel>

これで ラベルに色がつきます

添付プロパティ

一応 Tag にデータ複数入れれるようになったのですけど コードが長い ですよね

WPF では添付プロパティというプロパティを追加するものが用意されています
こっちを使えばもっと短くかけます

まずは準備
public static class AttProp
{
    public static string GetColor(DependencyObject obj)
    {
        return (string)obj.GetValue(ColorProperty);
    }

    public static void SetColor(DependencyObject obj, string value)
    {
        obj.SetValue(ColorProperty, value);
    }

    // Using a DependencyProperty as the backing store for Color.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ColorProperty =
        DependencyProperty.RegisterAttached("Color", typeof(string), typeof(AttProp), new PropertyMetadata(""));
}

追加するプロパティを定義します

色々書いていますが VisualStudio では 「propa」 にスニペット登録されているので簡単に作れます
スニペット展開して 型, 名前(Color のところ), 所属するクラスの型, 初期値 これらを好きに設定するだけで作れます


コントロールにプロパティの値を設定するときはこうなります
var label1 = new Label { Content = "red" };
AttProp.SetColor(label1, "red");

var label2 = new Label { Content = "blue" };
AttProp.SetColor(label2, "blue");

var label3 = new Label { Content = "green" };
AttProp.SetColor(label3, "green");

this.panel.Children.Add(label1);
this.panel.Children.Add(label2);
this.panel.Children.Add(label3);
添付プロパティがあるクラスの static メソッドでセットします

XAML だと
<StackPanel x:Name="panel">
    <Label local:AttProp.Color="red">red</Label>
</StackPanel>

トリガを設定するときは
<StackPanel x:Name="panel">
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Style.Triggers>
                <Trigger Property="local:AttProp.Color" Value="red">
                    <Setter Property="Foreground" Value="#f44"/>
                </Trigger>
                <Trigger Property="local:AttProp.Color" Value="blue">
                    <Setter Property="Foreground" Value="#38f"/>
                </Trigger>
                <Trigger Property="local:AttProp.Color" Value="green">
                    <Setter Property="Foreground" Value="#8e4"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
</StackPanel>

Converter よりもすごくシンプルにかけました

添付プロパティをクリアしたい


あとから気づいたのですが propa スニペットにはセットしたプロパティをクリアする方法がありません

null セットではなくセット前の状態にしたいときがあります
int や enum などの非 null 型の場合は特に です

スニペットにはないだけで GetValue/SetValue と同じように ClearValue 関数があります
public static void ClearXXXX(DependencyObject obj)
{
    obj.ClearValue(XXXXProperty);
}

このメソッドも追加しておけばクリアもできるようになります

中にはクリアするために DependencyProperty.UnsetValue をセットすれば良いと紹介しているところもありましたけど UnsetValue は型が object なので 添付プロパティが object 型じゃないとセットできませんでした

おまけ:DynamicObject

DynamicObject を継承したクラスを dynamic 型で使うとプロパティやメソッドの動きを自由に設定できます
呼び出した名前の文字列が渡されてメソッドが実行されるので Dictionary を内部でもっていてそこの値を取り出したり そこに値をセットしたりという使い方ができます

好きなクラスと DynamicObject を両方継承して好きなクラスにこの機能をつけられるといいのですが C# では 2 つのクラスを継承できません

なので 内部に目的の型のインスタンスを保持しておき プロパティやメソッドの操作がその型にあるものならそれを ないなら追加プロパティとして扱うようにすればできなくはないです

ただ ちゃんと元のオブジェクトのような動きをするように実装するのは面倒なうえ dynamic のみなので補完やコンパイル時のエラーチェックが減ってしまうデメリットはあります

プロパティだけならこういう感じ
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace liblib
{
    class Dynamic<T> : DynamicObject
        where T : new()
    {
        T instance;
        Dictionary<string, object> additional = new Dictionary<string, object>();

        public Dynamic()
        {
            this.instance = new T();
        }

        public Dynamic(T instance)
        {
            this.instance = instance;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var mem = typeof(T).GetMember(binder.Name, MemberTypes.Property | MemberTypes.Field, BindingFlags.Instance | BindingFlags.Public);

            if (mem.Length == 0)
            {
                this.additional[binder.Name] = value;
                return true;
            }

            var member = mem[0];

            if (member.MemberType == MemberTypes.Property)
            {
                var prop = (PropertyInfo)member;
                prop.SetValue(this.instance, value);
            }
            else if (member.MemberType == MemberTypes.Field)
            {
                var field = (FieldInfo)member;
                field.SetValue(this.instance, value);
            }
            else
            {
                return false;
            }

            return true;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var mem = typeof(T).GetMember(binder.Name, MemberTypes.Property | MemberTypes.Field, BindingFlags.Instance | BindingFlags.Public);

            if (mem.Length == 0)
            {
                result = this.additional[binder.Name];
                return true;
            }

            var member = mem[0];

            if (member.MemberType == MemberTypes.Property)
            {
                var prop = (PropertyInfo)member;
                result = prop.GetValue(this.instance);
            }
            else if (member.MemberType == MemberTypes.Field)
            {
                var field = (FieldInfo)member;
                result = field.GetValue(this.instance);
            }
            else
            {
                result = null;
                return false;
            }

            return true;
        }
    }
}
class Test
{
    public int a = 1;
    public string b { get; set; } = "abc";
}

dynamic t = new Dynamic<Test>();

Console.WriteLine(t.a);
Console.WriteLine(t.b);
t.a = 20;
t.b = "cba";
t.c = false;
Console.WriteLine(t.a);
Console.WriteLine(t.b);
Console.WriteLine(t.c);
1
abc
20
cba
False