C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2010/01/26 02:18

ByRef parameter and C# - 猫とC#について書くmatarilloの日記

MethodInfo.Invoke で動的に実行したメソッド内で例外が発生した場合 out 引数が書き換えられない、という話。
Reflection.Emit でゴリゴリ自前実装してみたものをコメントに投稿しましたが、インスタンスメソッドの動的実行に対応できてなかったので、対応したコードをこちらで晒しときます。あと、NyaRuRu さんからの指摘事項含め、リファクタリングもしときました。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Invoker = System.Func<object, object[], object>;

public static class MethodInfoExtension
{
    public static object InvokeStrictly(this MethodInfo source, object obj, params object[] parameters)
    {
        if (source == null)
        {
            throw new NullReferenceException();
        }
        int parametersLength = (parameters != null) ? parameters.Length : 0;
        if (source.GetParameters().Length != parametersLength)
        {
            throw new ArgumentException("引数 parameters の要素数とメソッドのパラメータ数が一致しません。");
        }

        var invokerBuilder = new DynamicMethod("DynamicMethod", typeof(object), new[] { typeof(object), typeof(object[]) }, true);
        GenerateCode(source, invokerBuilder);
        var invoker = (Invoker)invokerBuilder.CreateDelegate(typeof(Invoker));

        return invoker(obj, parameters);
    }

    private static void GenerateCode(MethodInfo source, DynamicMethod invokerBuilder)
    {
        ILGenerator ilGenerator = invokerBuilder.GetILGenerator();

        List<LocalBuilder> paramLocalBuilderList = GenerateCodeForLocalVariableDeclarationsForParameters(source, ilGenerator);
        LocalBuilder resultLocal = GenerateCodeForLocalVariableDeclarationForResult(ilGenerator);
        GenerateCodeForSubstitutingsParameterToLocalVariable(paramLocalBuilderList, ilGenerator);
       
        /* Begin try block. */
        Label exBlockLabel = ilGenerator.BeginExceptionBlock();
        GenerateCodeForCallingSourceMethod(source, paramLocalBuilderList, resultLocal, ilGenerator);
        ilGenerator.Emit(OpCodes.Leave, exBlockLabel);
        /* End try block. */

        /* Begin finally block. */
        ilGenerator.BeginFinallyBlock();
        GenerateCodeForRestorationParameters(paramLocalBuilderList, ilGenerator);
        ilGenerator.EndExceptionBlock();
        /* End finally block. */

        ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Ret);
    }

    private static List<LocalBuilder> GenerateCodeForLocalVariableDeclarationsForParameters(MethodInfo source, ILGenerator ilGenerator)
    {
        List<LocalBuilder> localBuilderList = new List<LocalBuilder>();
        ParameterInfo[] paramInfos = source.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            var paramType = paramInfo.ParameterType;
            var localType = paramType.IsByRef ? paramType.GetElementType() : paramType;

            localBuilderList.Add(ilGenerator.DeclareLocal(localType));
        }
        return localBuilderList;
    }

    private static LocalBuilder GenerateCodeForLocalVariableDeclarationForResult(ILGenerator ilGenerator)
    {
        return ilGenerator.DeclareLocal(typeof(object), false);
    }

    private static void GenerateCodeForSubstitutingsParameterToLocalVariable(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        for (int i = 0; i < paramLocalBuilderList.Count; i++)
        {
            var paramLocalBuilder = paramLocalBuilderList[i];

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);

            Label passLabel = ilGenerator.DefineLabel();
            ilGenerator.Emit(OpCodes.Brfalse, passLabel);

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            ilGenerator.Emit(OpCodes.Unbox_Any, paramLocalBuilder.LocalType);
            ilGenerator.Emit(OpCodes.Stloc_S, paramLocalBuilder);

            ilGenerator.MarkLabel(passLabel);
        }
    }

    private static void GenerateCodeForCallingSourceMethod(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, LocalBuilder resultLocal, ILGenerator ilGenerator)
    {
        if (!source.IsStatic)
        {
            GenerateCodeForPushingContextObject(source, ilGenerator);
        }
        GenerateCodeForPushingParameters(source, paramLocalBuilderList, ilGenerator);

        ilGenerator.Emit(OpCodes.Call, source);
        if (source.ReturnType == typeof(void))
        {
            ilGenerator.Emit(OpCodes.Ldnull);
        }
        ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
    }

    private static void GenerateCodeForPushingContextObject(MethodInfo source, ILGenerator ilGenerator)
    {
        ilGenerator.Emit(OpCodes.Ldarg_0);
        if (source.DeclaringType.IsValueType)
        {
            ilGenerator.Emit(OpCodes.Unbox, source.DeclaringType);
        }
        else
        {
            ilGenerator.Emit(OpCodes.Castclass, source.DeclaringType);
        }
    }

    private static void GenerateCodeForPushingParameters(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        ParameterInfo[] paramInfos = source.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            var paramLocalBuilder = paramLocalBuilderList[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;

            if (paramIsByRef)
            {
                ilGenerator.Emit(OpCodes.Ldloca_S, paramLocalBuilder);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
            }
        }
    }

    private static void GenerateCodeForRestorationParameters(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        for (int i = 0; i < paramLocalBuilderList.Count; i++)
        {
            var paramLocalBuilder = paramLocalBuilderList[i];
            var localType = paramLocalBuilder.LocalType;

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
            if (localType.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, localType);
            }
            ilGenerator.Emit(OpCodes.Stelem, typeof(object));
        }
    }
}



using System;
using System.Reflection;

class P
{
    public static void Method(out string arg)
    {
        arg = "out";
        throw new Exception();
    }

    static void Main()
    {
        object[] args = new object[1];
        try
        {
            MethodInfo targetMethod = typeof(P).GetMethod("Method");
            targetMethod.InvokeStrictly(null, args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            Console.WriteLine();
        }
        Console.WriteLine(args[0]);
        Console.ReadLine();
    }
}



こーゆー IL を動的に構築するコードって、もしかしてメソッド分割しない方が見やすい?
タグ: .NET C# Reflection IL
2010/01/07 05:46
今年は、会社を辞めるための準備を着々と進めます。
転職活動とは少し違って、やや長期的な活動になると思います。

何はともあれ、今年もよろしくお願いします。
タグ:
2009/11/10 01:48
約4ヶ月ぶりの更新です。
しぶとく生きてます。
最近は C# 触れてないです。
RSS フィードも未読が 2000 件くらいあって読む気がしません。
まるで虐待です。
まぁそんなことはいいとして、これからはもう少しまともに更新していきたいと思っています。でも思っているだけです。たぶんまだしばらくはあまり更新できません、ごめんなさい。
タグ:
2009/06/30 01:13
突然ですが、アフィリエイトを始めることにしました。近々サイドバーにウィジェットを配置する予定ですが、まずは最近気になっているこの本をここでご紹介したいと思います。

テストコードの用意されていないコードは全てレガシーコードと呼ぶそうです。(なので新規開発でもテストコードが用意されていなければレガシーコードになります。)
レガシーコードに手を入れる時は、手を入れる箇所 (と周辺のコード) にテストコードを用意しリファクタリングを行うと良い、という話はよく聞きますが、そうは言っても具体的にはどのようにテスト可能なコードに導いていけば良いのか、どの程度まで足を踏み込んで良いのか等、悩みの種はたくさんです。
しかし、本書を読めばきっとそういう悩みが解決されるだろうと僕は勝手に予想しています。これでレガシーコードも余裕です。

ちなみに Amazon には発売日がまだ表示されていないようですが、 (←勘違いだったようです) 発売日は 7/14 らしいです。
2009/06/29 19:47
マスターページ内の link タグの href 属性では、チルダ (~) を使った仮想パス形式、もしくはマスターページの配置場所からの相対パス形式で記述しておくと、実行時に適切な相対パスに変換してくれます。(追記:head タグに runat="server" を記述していない場合は変換されませんので注意してください。)
しかし、script タグの src 属性に関しては、チルダを使った仮想パスや相対パスで記述しても変換が一切行われません。
なので、Control.ResolveClientUrl メソッドVirtualPathUtility.ToAbsolute メソッドを使用して変換する必要があります。幸い、HTML タグの属性値ならば <%= %> が使用できるので (コードのハイライトやインテリセンスは効きませんが) 簡単に対処できます。

Site.Master
<head runat="server">
    <title><asp:Localize runat="server" Text="<%$ Resources:CommonResource, SystemName %>" /></title>
    <link href="Common.css" type="text/css" rel="stylesheet" />
    <link href="Site.css"   type="text/css" rel="stylesheet" />
    <script src="<%= this.ResolveClientUrl("~/Common.js") %>" type="text/javascript"></script>
    <script src="<%= this.ResolveClientUrl("~/Site.js") %>"   type="text/javascript"></script>
    <asp:ContentPlaceHolder ID="HeadPlaceHolder" runat="server" />
</head>


なお、スタイルシートに関しては Thema 機能を使用すれば link タグを記述する必要はなくなりますが、全てのスタイルシートが読み込まれてしまうため、(スタイルシートの) クラス名の衝突の回避に一工夫必要だったりと管理が複雑化します。テーマの切り替えが不要ならば使用しない方が良いかと思います。


[関連]
C#と諸々 ルートディレクトリを示す ~ 演算子
タグ: .NET C# ASP.NET