21854


`MethodImpl`属性の、`Synchronized`等のオプションを指定するとメソッド自体が排他ロックされるわけですが、こういった属性を加える事でメソッド自体に影響を与えるカスタム属性はどのようにしたら作れますか？  
それから、属性を与えることで戻り値を操作することは出来ますか？

例)

[code]

    [Increment(+5)]
    public void Method(int i) {
        return i;
    }
    
    public void Main() {
        int j = Method(3);
        // j == 8 :: true
    }
    
[/code]


CC BY-SA 3.0
2016-02-02T01:44:36.170
21599
2016-02-29T07:34:24.290
2016-02-29T07:34:24.290
14228
14228
question
c# .net
MethodImplAttributeのように、プログラムの動作に影響を与えるカスタム属性は作れますか？


できません。カスタム属性は実行時にメタデータとして参照するだけのものです。  
例に挙げられた`MethodImplAttribute`を含む`System.Runtime.CompilerServices`名前空間はコンパイラーと協調して使用されるもので、逆にコンパイラーにとって未知の属性については無視されます。


CC BY-SA 3.0
2016-02-02T02:12:10.883
21600
2016-02-02T02:12:10.883
4236
21599
answer


カスタム属性によってメソッドの処理を変更するという発想は「アスペクト指向プログラミング」と呼ばれる手法で、.NETにおいては

  * コンパイル後/コンパイル中にコードの書き換えを行う
  * 実行時にメソッド呼び出し動作を変更できるオブジェクトを使う

というような方法があります。上の方法は.NET中間言語の知識が必要になりますので非常に敷居が高くなりますが、下はたとえば`RealProxy`クラスを使えば比較的簡単に実装できます。

[code]

    using System;
    using System.Reflection;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Activation;
    using System.Runtime.Remoting.Messaging;
    using System.Runtime.Remoting.Proxies;
    using System.Runtime.Remoting.Services;
    
    // サンプルクラス1
    [HogeProxy]
    public class Hoge : ContextBoundObject
    {
        [Increment(100)]
        public int Get1()
        {
            return 1;
        }
    
        public int Property2
        {
            [Increment(200)]
            get
            {
                return 2;
            }
        }
    }
    
    // サンプルクラス2
    [HogeProxy]
    public class Fuga : ContextBoundObject
    {
        [Increment(300)]
        public int Get3()
        {
            return 3;
        }
    }
    
    // 設定するための属性
    public class IncrementAttribute : Attribute
    {
        public IncrementAttribute(int value)
        {
            Value = value;
        }
    
        public int Value { get; }
    }
    
[/code]

このクラスを下のように実行すると、上の実装とは異なる値が返されます。

[code]

    static void Main(string[] args)
    {
        // 上の実装とは異なる値が表示される。
        Console.WriteLine(new Hoge().Get1());    // 101
        Console.WriteLine(new Hoge().Property2); // 202
        Console.WriteLine(new Fuga().Get3());    // 303
    
        Console.ReadKey();
    }
    
[/code]

これは下の`ProxyAttribute`を使用して、実際のオブジェクトではなくプロキシと呼ばれるインスタンスを返しているからです。

[code]

    public class HogeProxyAttribute : ProxyAttribute
    {
        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            var obj = base.CreateInstance(serverType);
            return (MarshalByRefObject)new HogeProxy(serverType, obj).GetTransparentProxy();
        }
    }
    
[/code]

このプロキシではメソッドが呼び出されたときにカスタム属性の有無を調べ、存在すれば戻り値を書き換えています。

[code]

    public class HogeProxy : RealProxy
    {
        private readonly MarshalByRefObject _Object;
    
        public HogeProxy(Type type, MarshalByRefObject obj)
            : base(type)
        {
            _Object = obj;
        }
    
        public override IMessage Invoke(IMessage msg)
        {
            var ctrMsg = msg as IConstructionCallMessage;
            if (ctrMsg != null)
            {
                // コンストラクター呼び出しの処理
    
                var dp = RemotingServices.GetRealProxy(_Object);
                dp.InitializeServerObject(ctrMsg);
    
                var tp = (MarshalByRefObject)GetTransparentProxy();
    
                return EnterpriseServicesHelper.CreateConstructionReturnMessage(ctrMsg, tp);
            }
    
            var mr = (IMethodCallMessage)msg;
    
            // メソッド呼び出し時の処理
    
            // メソッドを実行する
            var mi = (MethodInfo)mr.MethodBase;
            var ret = mi.Invoke(_Object, mr.Args);
    
            // 戻り値がintでIncrementAttributeの指定がある場合、戻り値を変更する
            if (ret is int)
            {
                var attr = mi.GetCustomAttribute<IncrementAttribute>();
    
                if (attr != null)
                {
                    ret = attr.Value + (int)ret;
                }
            }
    
            // 実行結果を返す
            return new ReturnMessage(ret, null, 0, mr.LogicalCallContext, mr);
        }
    }
    
[/code]

ただしこのような動的処理は通常の実装と比べて動作が遅くなります。可読性も下がりますし、技術的には可能であっても数値計算程度で使用するべきではありません。


CC BY-SA 3.0
2016-02-08T10:42:00.270
21854
2016-02-08T10:42:00.270
5750
21599
answer