C#7時代の依存関係プロパティと添付プロパティのコードスニペット

概要

VisualStudioには標準で依存関係プロパティと添付プロパティのコードスニペットが組み込まれています。
しかし、これらのコードスニペットは古くイマイチな部分があります。
そこで最新のC#7の文法に合わせた新しいコードスニペットを定義します。

とりあえず使ってみたい場合は最後の使用方法だけ参照ください。

新旧コードスニペット実行結果の比較

まず、VisualStudio組込バージョンと今回作ったNewバージョンの実行結果を比較します。

依存関係プロパティ

propdp.snippet(VS組込)
public string Name
{
    get { return (string)GetValue(NameProperty); }
    set { SetValue(NameProperty, value); }
}

// Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(ownerclass), new PropertyMetadata(""));

DependencyProperty.snippet(New)
#region Name依存関係プロパティ
public string Name
{
    get => (string)GetValue(NameProperty);
    set => SetValue(NameProperty, value);
}
public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register(nameof(Name), typeof(string), typeof(MyClass), new PropertyMetadata(default(string)));
#endregion

添付プロパティ

propa.snippet(VS組込)
public static string GetName(DependencyObject obj)
{
    return (string)obj.GetValue(NameProperty);
}

public static void SetName(DependencyObject obj, string value)
{
    obj.SetValue(NameProperty, value);
}

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

AttachedProperty.snippet(New)
#region Name添付プロパティ
public static string GetName(DependencyObject obj) => (string)obj.GetValue(NameProperty);
public static void SetName(DependencyObject obj, string value) => obj.SetValue(NameProperty, value);
public static readonly DependencyProperty NameProperty =
    DependencyProperty.RegisterAttached("Name", typeof(string), typeof(MyClass), new PropertyMetadata(default(string)));
#endregion

変更点の解説

regionで囲って見やすく

#region Name依存関係プロパティ

これは好みが分かれるところですが、複数の依存関係or添付プロパティが並んだ場合に読みやすくなります。
そもそもregionが必要になること自体が言語設計に問題があることを

Get Set をExpression-bodiedに

//依存関係プロパティ
get => (string)GetValue(NameProperty);

//添付プロパティ
public static string GetName(DependencyObject obj) => (string)obj.GetValue(NameProperty);

実はこのコードスニペット全体でC#7が必要なのは依存関係プロパティのgetterのみ

コメント削除

依存関係プロパティ自体の説明を個別のプロパティの中に入れる必要性無いですよね。

ownerclassが自動で現在のクラス名に

DependencyProperty.Register(……, typeof(MyClass), ……

実行結果だけだと前と変わらないように見えますが、コードスニペットを実行した場所のクラス名が自動で入るようになりました。
後述するコードスニペット内の<Function>ClassName()</Function>の機能です。
コンストラクタのコードスニペットctorと同じ動きです。

nameof演算子(依存関係プロパティのみ)

DependencyProperty.Register(nameof(Name), ……

文字列からnameof演算子にすることでより安全になります。

添付プロパティでも使用したかったのですが、そもそも添付プロパティには
$プロパティ名$Propertyはいても$プロパティ名$はいなかったので、できませんでした。

どうしても添付プロパティでも文字列指定を削除したい場合は
プロパティ名を取得するメソッドを別に用意して、引数にCallerMemberNameを使えばできます。

public static readonly DependencyProperty AgeProperty =
    DependencyProperty.RegisterAttached(GetPropertyCoreName(), typeof(int), typeof(MyClass), new PropertyMetadata(default(int)));

private static string GetPropertyCoreName([CallerMemberName] string propName = null)
    => propName.Substring(0, propName.Length - "Property".Length);

PropertyMetadata既定値を0からdefault(型)に変更

DependencyProperty.Register(…… new PropertyMetadata(default(string)));

PropertyMetadataの既定値が型に関係なく常に0だったのをdefault(型)に変更
コードスニペットのLiteralのDefaultは他のLiteralの値を使用できないので、型名Literalを元にMetadata既定値にしています。

ただしMetadata既定値用のLiteralはDefaultを空にして残してあるので、既定値を変更したい場合はジャンプすることができます。

ちなみにうっかり以下のようにすると既定値がnullになります。

DependencyProperty.Register(…… new PropertyMetadata(default));

使用方法

既存のコードスニペットを削除します。

場所は以下のところに書いてあります。
VisualStudio上Menu>ツール>コードスニペットマネージャ>NetFX30

私の環境(VS2017)では以下の場所でした。
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC#\Snippets\1041\NetFX30

次に以下2つのコードスニペットをファイルに保存してコードスニペットマネージャからインポートします。

DependencyProperty.snippet-GitHub
AttachedProperty.snippet-GitHub

コードスニペット

DependencyProperty.snippet
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>DependencyProperty</Title>
      <Shortcut>propdp</Shortcut>
      <Description>依存関係プロパティを作成します</Description>
      <Author>soi</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>プロパティの型</ToolTip>
          <Default>int</Default>
        </Literal>
        <Literal>
          <ID>property</ID>
          <ToolTip>プロパティ名</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
        <Literal>
          <ID>ownerclass</ID>
          <Function>ClassName()</Function>
          <ToolTip>プロパティを登録している所有者の型</ToolTip>
          <Default>ignor this element</Default>
        </Literal>
        <Literal>
          <ID>defaultvalue</ID>
          <ToolTip>>PropertyMetadataの既定値</ToolTip>
          <Default></Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[
#region $property$依存関係プロパティ
public $type$ $property$
{
    get => ($type$)GetValue($property$Property);
    set => SetValue($property$Property, value);
}
public static readonly DependencyProperty $property$Property =
    DependencyProperty.Register(nameof($property$), typeof($type$), typeof($ownerclass$), new PropertyMetadata($defaultvalue$default($type$)));
#endregion
$end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

AttachedProperty.snippet
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>AttachedProperty</Title>
      <Shortcut>propa</Shortcut>
      <Description>添付プロパティを作成します</Description>
      <Author>soi</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>プロパティの型</ToolTip>
          <Default>int</Default>
        </Literal>
        <Literal>
          <ID>property</ID>
          <ToolTip>プロパティ名</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
        <Literal>
          <ID>ownerclass</ID>
          <Function>ClassName()</Function>
          <ToolTip>プロパティを登録している所有者の型</ToolTip>
          <Default>ignor this element</Default>
        </Literal>
        <Literal>
          <ID>defaultvalue</ID>
          <ToolTip>PropertyMetadataの既定値</ToolTip>
          <Default></Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[
#region $property$添付プロパティ
public static $type$ Get$property$(DependencyObject obj) => ($type$)obj.GetValue($property$Property);
public static void Set$property$(DependencyObject obj, $type$ value) => obj.SetValue($property$Property, value);
public static readonly DependencyProperty $property$Property =
    DependencyProperty.RegisterAttached("$property$", typeof($type$), typeof($ownerclass$), new PropertyMetadata($defaultvalue$default($type$)));
#endregion
$end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.