漫坊亭

社会の底辺プログラマ

CallerInfoを、.NET40 で使う

HDD整理中・・・

いまだに.NET40縛りでのコーディングが発生することがある。 制御系は古いもののメンテが多い(はやくXP消えてくれ)

開発ホストがVS2012以降なら、新しいC#の機能が使える場合もある。 もちろん、実機ではデバッグできない。

CallerInfo

ログ出力やWPFでは、もはや必須の機能である。これって、実はコンパイラの機能らしい。なので、Visual Studio 2012以降なら、ターゲットフレームワークが4.5でなくても使えないわけではない。

namespace System.Runtime.CompilerServices
{
#if !NET_45_OR_GREATER
    /// <summary>メソッドの呼び出し元のメソッドまたはプロパティの名前を取得できるようにします。</summary>
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerMemberNameAttribute : Attribute
    {
    }

    /// <summary>呼び出し元を含むソースファイルの完全パスを取得します。</summary>
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerFilePathAttribute : Attribute
    {
    }

    /// <summary>割り当てメソッドが呼び出されるソース ファイルの行番号を取得します。</summary>
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerLineNumberAttribute : Attribute
    {
    }
#endif
}

これをプロジェクトに追加すると、コンパイラが良きに計らってくれる。

class Program
{
    static void Main(string[] args)
    {
        var obj = new Foo();
        obj.Bar("テスト");

        Console.ReadLine();
    }
}

public class Foo
{
    public void Bar(
        string message,
        [CallerMemberName] string memberName = null,
        [CallerFilePath] string filePath = null,
        [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine("CallerMemberName = {0}", memberName);
        Console.WriteLine("CallerFilePath   = {0}", filePath);
        Console.WriteLine("CallerLineNumber = {0}", lineNumber);
        Console.WriteLine("CallerLineNumber = {0}", message);
    }
}

これで、ターゲットフレームワークが固定なら良いが、プロジェクトを.Net4.5でも使いたい場合は、ビルド時に判定する必要がある。

.NETのバージョン判定を行う。

TargetFrameworkVersionを文字列として分解し、バージョンを示すシンボルを定義する。C言語プリプロセッサのように内容を入れることができないので、個々のシンボルを作る。

  • NET_20
  • NET_30
  • NET_35
  • NET_40
  • NET_45
  • NET_451
  • NET_20_OR_GREATER
  • NET_30_OR_GREATER
  • NET_35_OR_GREATER
  • NET_40_OR_GREATER
  • NET_45_OR_GREATER

どこか(たぶんstack overflowとか)で拾ったものをベースに、4.5と4.51を追加したような記憶があるので、原著者に感謝!

<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    
    <!-- NET Frameworkのバージョンに関する定数を定義する。 -->
    <!-- NET_20, NET_30, NET_35, NET_40, NET_45, NET_451, -->
    <!-- NET_20_OR_GREATER, NET_30_OR_GREATER, NET_35_OR_GREATER, NET_40_OR_GREATER, NET_45_OR_GREATER -->

    <!-- メジャーバージョン、マイナーバージョン、ビルド番号の結果を入れる -->
    <VerMajor>0</VerMajor>
    <VerMinor>0</VerMinor>
    <VerBuild>0</VerBuild>

    <!-- Pos1:最初のピリオドの位置 / Pos2:次のピリオドの位置 / Pos1n,Pos2nは、それぞれのPosに+1している -->
    <Pos1>-1</Pos1>
    <Pos1n>-1</Pos1n>
    <Pos2>-1</Pos2>
    <Pos2n>-1</Pos2n>

    <!-- v4.5.1とかv2.0とか入っているので、まずは'v'を除去 -->
    <Tmp>$(TargetFrameworkVersion.Replace('v', ''))</Tmp>

    <!-- ピリオドがなければPos1に-1が入るので、Pos2に突入させない-->
    <Pos1>$(Tmp.IndexOf("."))</Pos1>
    <Pos1n>$([MsBuild]::Add($(Pos1), 1))</Pos1n>
    <Pos2 Condition="0 &lt;= $(Pos1)">$(Tmp.IndexOf(".", $([MsBuild]::Add($(Pos1), 1))))</Pos2>
    <Pos2n>$([MsBuild]::Add($(Pos2), 1))</Pos2n>
    
    <!-- 最初のピリオドがあれば、そこまでを VerMajor にする。ピリオドがなければ、全てを VerMajor にする。-->
    <VerMajor  Condition="0 &lt;= $(Pos1)">$(Tmp.SubString(0, $(Pos1)))</VerMajor>
    <VerMajor  Condition="0 &gt;  $(Pos1)">$(Tmp)</VerMajor>

    <!-- 次のピリオドがあれば、 最初のピリオドから次のピリオドまでを、VerMinorにする。なければ、残りをVerMinorにする-->
    <VerMinor  Condition="0 &lt;= $(Pos2)">$(Tmp.SubString($(Pos1n), $([MsBuild]::Subtract($(Pos2), $(Pos1n)))))</VerMinor>
    <VerMinor  Condition="0 &gt;  $(Pos2) And 0 != $(Pos1n)">$(Tmp.SubString($(Pos1n)))</VerMinor>
    
    <!--2つ目のピリオドがあれば、そこから後ろを VerBuild にする-->
    <VerBuild  Condition="0 &lt;= $(Pos2)">$(Tmp.SubString($(Pos2n)))</VerBuild>

    <!-- VerMajor,VerMinor,VerBuildを用いて、定数を作成する。 -->
    <DefineConstants>$(DefineConstants);NET_$(VerMajor)$(VerMinor)$(VerBuild)</DefineConstants>
    <DefineConstants Condition="2 &lt;= $(VerMajor)">$(DefineConstants);NET_20_OR_GREATER</DefineConstants>
    <DefineConstants Condition="3 &lt;= $(VerMajor)">$(DefineConstants);NET_30_OR_GREATER</DefineConstants>
    <DefineConstants Condition="3 &lt;= $(VerMajor) And 5 &lt;= $(VerMinor)">$(DefineConstants);NET_35_OR_GREATER</DefineConstants>
    <DefineConstants Condition="4 &lt;= $(VerMajor)">$(DefineConstants);NET_40_OR_GREATER</DefineConstants>
    <DefineConstants Condition="4 &lt;= $(VerMajor) And 5 &lt;= $(VerMinor)">$(DefineConstants);NET_45_OR_GREATER</DefineConstants>

  </PropertyGroup>

</Project>

これをNetFrameworkVersion.targetsという名前でプロジェクトに追加し、.csprojに下記の1行を追加する。

<Import Project="$(SolutionDir)NetFrameworkVersion.targets" />

ソース一式(拡張子をzipにして展開する)
f:id:jfactory:20160212093217j:plain