3

リフレクションによって、文字列パターンを使用してオブジェクトから値を取得および設定できるクラスを作成しています。クラスは複雑なパターンでもうまく機能しますが、解決方法や回避策がわからない予期しない動作が発生しました。

基本的に、クラスが値型であるフィールドまたはプロパティにアクセスしている場合、すべてが機能しますが、値型のコピーで動作します。実際、文字列パターンを使用して値を設定しようとすると、実際の値の型が更新されません。

クラスはobject参照とMemberInfoインスタンスを保持します (これらのオブジェクトは、ルート オブジェクトのアクセス パターンを分析することによって取得されます)。このようにして、インスタンスMemberInfoから開始して指定されたメンバーを取得または設定できます。object

private static object GetObjectMemberValue(object obj, MemberInfo memberInfo, object[] memberArgs)
{
    if (memberInfo == null)
        throw new ArgumentNullException("memberInfo");

    // Get the value
    switch (memberInfo.MemberType) {
        case MemberTypes.Field: {
                FieldInfo fieldInfo = (FieldInfo)memberInfo;

                if (fieldInfo.FieldType.IsValueType) {
                    TypedReference typedReference = __makeref(obj);
                    return (fieldInfo.GetValueDirect(typedReference));
                } else
                    return (fieldInfo.GetValue(obj));
            }
        case MemberTypes.Property:
            return (((PropertyInfo)memberInfo).GetValue(obj, memberArgs));
        case MemberTypes.Method:
            return (((MethodInfo)memberInfo).Invoke(obj, memberArgs));
        default:
            throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
    }
}

private static void SetObjectMemberValue(object obj, MemberInfo memberInfo, params object[] memberArgs)
{
    if (memberInfo == null)
        throw new ArgumentNullException("memberInfo");

    // Set the value
    switch (memberInfo.MemberType) {
        case MemberTypes.Field: {
                FieldInfo fieldInfo = (FieldInfo)memberInfo;

                if (fieldInfo.FieldType.IsValueType) {
                    TypedReference typedReference = __makeref(obj);
                    fieldInfo.SetValueDirect(typedReference, memberArgs[0]);
                } else
                    fieldInfo.SetValue(obj, memberArgs[0]);
            } break;
        case MemberTypes.Property:
            ((PropertyInfo)memberInfo).SetValue(obj, memberArgs[0], null);
            break;
        case MemberTypes.Method:
            ((MethodInfo)memberInfo).Invoke(obj, memberArgs);
            break;
        default:
            throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
    }
}

objパラメータが構造体値の場合、エラーが発生します: I get/set from the boxed value .

どうすればこれを回避できますか? 私はすでにこの質問をチェックしましたが、成功しませんでした (フィールド管理のコードを見ることができます): フィールド値をオブジェクト変数に割り当てるため、ボックス化はすべて同じように発生します。

問題のクラスの完全なコードは次のとおりです。

// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace Derm
{
    /// <summary>
    /// Class able to read and write a generic object.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This class supports the access to one of the following:
    /// - A specific object field
    /// - A specific object property (even indexed)
    /// - A specific object method (even with arguments)
    /// </para>
    /// </remarks>
    public class ObjectAccessor
    {
        #region Constructors

        /// <summary>
        /// Construct an ObjectAccessor that access to an object's field or property.
        /// </summary>
        /// <param name="container">
        /// A <see cref="System.Object"/> that specify a generic member.
        /// </param>
        /// <param name="memberPattern">
        /// A <see cref="System.String"/> that specify the pattern of the member of <paramref name="container"/>.
        /// </param>
        public ObjectAccessor(object container, string memberPattern)
        {
            if (container == null)
                throw new ArgumentNullException("container");
            if (memberPattern == null)
                throw new ArgumentNullException("memberPattern");

            // Store member pattern
            mMemberPattern = memberPattern;

            Dictionary<int, string> stringMap = new Dictionary<int,string>();
            object containerMember = container;
            int stringMapIndex = 0;

            // Remove (temporarly) strings enclosed by double-quotes
            memberPattern = Regex.Replace(memberPattern, "\"[^\\\"]*\"", delegate(Match match) {
                stringMap[stringMapIndex++] = match.Value;

                return (String.Format("{{{0}}}", stringMapIndex - 1));
            });

            string[] members = Regex.Split(memberPattern, @"\.");

            // Restore strings enclosed by double-quotes
            for (int i = 0; i < members.Length; i++ ) {
                members[i] = Regex.Replace(members[i], @"{(?<StringOrder>\d+)}", delegate(Match match) {
                    return (stringMap[Int32.Parse(match.Groups["StringOrder"].Value)]);
                });
            }

            if (members.Length > 1) {
                StringBuilder containerMemberPattern = new StringBuilder(memberPattern.Length);

                for (int i = 0; i < members.Length - 1; i++ ) {
                    MemberInfo memberInfo;
                    object[] memberArgs;

                    // Pattern for exception message
                    containerMemberPattern.AppendFormat(".{0}", members[i]);
                    // Access to the (intermediate) member
                    GetObjectMember(containerMember, members[i], out memberInfo, out memberArgs);
                    // Get member value
                    containerMember = GetObjectMemberValue(containerMember, memberInfo, memberArgs);
                    if (containerMember == null)
                        throw new InvalidOperationException(String.Format("the field {0} is null", containerMemberPattern.ToString()));
                    if ((memberInfo.MemberType != MemberTypes.Field) && (containerMember.GetType().IsValueType == true))
                        throw new NotSupportedException("invalid pattern becuase operating on strcuture copy");
                }
            }

            // Store container object
            mContainer = container;
            // Store object
            mObject = containerMember;
            // Get member
            GetObjectMember(mObject, members[members.Length - 1], out mMember, out mMemberArgs);
        }

        #endregion

        #region Object Access

        /// <summary>
        /// Get the type of the accessed member.
        /// </summary>
        public Type MemberType 
        {
            get
            {
                switch (mMember.MemberType) {
                    case MemberTypes.Field:
                        return (((FieldInfo)mMember).FieldType);
                    case MemberTypes.Property:
                        return (((PropertyInfo)mMember).PropertyType);
                    default:
                        throw new NotSupportedException(mMember.MemberType + " is not supported");
                }
            }
        }

        /// <summary>
        /// Get the value of the object member.
        /// </summary>
        /// <returns></returns>
        public object Get()
        {
            switch (mMember.MemberType) {
                case MemberTypes.Field: {
                        FieldInfo fieldInfo = (FieldInfo)mMember;

                        if (fieldInfo.FieldType.IsValueType) {
                            object referenceObject = mObject;
                            TypedReference typedReference = __makeref(referenceObject);
                            return (fieldInfo.GetValueDirect(typedReference));
                        } else
                            return (fieldInfo.GetValue(mObject));
                    }
                case MemberTypes.Property:
                    if (((PropertyInfo)mMember).CanRead == false)
                        throw new InvalidOperationException("write-only property");
                    return (((PropertyInfo)mMember).GetValue(mObject, null));
                default:
                    throw new NotSupportedException(mMember.MemberType + " is not supported");
            }
        }

        /// <summary>
        /// Set the value of the object member.
        /// </summary>
        /// <param name="value"></param>
        public void Set(object value)
        {
            switch (mMember.MemberType) {
                case MemberTypes.Field: {
                        FieldInfo fieldInfo = (FieldInfo)mMember;

                        if (fieldInfo.FieldType.IsValueType) {
                            object referenceObject = mObject;
                            TypedReference typedReference = __makeref(referenceObject);
                            fieldInfo.SetValueDirect(typedReference, value);
                        } else
                            fieldInfo.SetValue(mObject, value);
                    } break;
                case MemberTypes.Property:
                    if (((PropertyInfo)mMember).CanWrite == false)
                        throw new InvalidOperationException("read-only property");
                    ((PropertyInfo)mMember).SetValue(mObject, value, null);
                    break;
                default:
                    throw new NotSupportedException(mMember.MemberType + " is not supported");
            }
        }

        /// <summary>
        /// The object used for getting the object implementing <see cref="mMember"/>. In simple cases
        /// it equals <see cref="mObject"/>.
        /// </summary>
        private readonly object mContainer;

        /// <summary>
        /// The object that specify the field/property pointed by <see cref="mMember"/>.
        /// </summary>
        private readonly object mObject;

        /// <summary>
        /// The pattern used for getting/setting the member of <see cref="mObject"/>.
        /// </summary>
        private readonly string mMemberPattern;

        /// <summary>
        /// Field, property or method member of <see cref="mObject"/>.
        /// </summary>
        private readonly MemberInfo mMember;

        /// <summary>
        /// Arguments list specified at member invocation.
        /// </summary>
        private readonly object[] mMemberArgs;

        #endregion

        #region Object Member Access

        /// <summary>
        /// Access to an object member.
        /// </summary>
        /// <param name="obj">
        /// A <see cref="System.Object"/> which type defines the underlying member.
        /// </param>
        /// <param name="memberPattern">
        /// A <see cref="System.String"/> that specify how the member is identified. For methods and indexed properties, the arguments
        /// list is specified also.
        /// </param>
        /// <param name="memberInfo">
        /// A <see cref="System.Reflection.MemberInfo"/> that represent the member.
        /// </param>
        /// <param name="memberArgs">
        /// An array of <see cref="System.Object"/> that represent the argument list required for calling a method or an indexed
        /// property.
        /// </param>
        private static void GetObjectMember(object obj, string memberPattern, out MemberInfo memberInfo, out object[] memberArgs)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");
            if (memberPattern == null)
                throw new ArgumentNullException("memberPattern");

            Type objType = obj.GetType();
            Match methodMatch;

            if ((methodMatch = sCollectionRegex.Match(memberPattern)).Success || (methodMatch = sMethodRegex.Match(memberPattern)).Success) {
                MemberInfo[] members = objType.GetMember(methodMatch.Groups["MethodName"].Value);
                ParameterInfo[] methodArgsInfo;
                int bestMemberIndex = 0;

                if ((members == null) || (members.Length == 0))
                    throw new InvalidOperationException(String.Format("no property/method {0}", memberPattern));

                string[] args = Regex.Split(methodMatch.Groups["MethodArgs"].Value, " *, *");

                if (members.Length != 1) {
                    Type[] argsType = new Type[args.Length];

                    bestMemberIndex = -1;

                    // Try to guess method arguments type to identify the best overloaded match
                    for (int i = 0; i < args.Length; i++)
                        argsType[i] = GuessMethodArgumentType(args[i]);

                    if (Array.TrueForAll<Type>(argsType, delegate(Type type) { return (type != null); })) {
                        for (int i = 0; i < members.Length; i++) {
                            if (members[i].MemberType == MemberTypes.Property) {
                                methodArgsInfo = ((PropertyInfo)members[i]).GetIndexParameters();
                                Debug.Assert((methodArgsInfo != null) && (methodArgsInfo.Length > 0));
                            } else if (members[i].MemberType == MemberTypes.Method) {
                                methodArgsInfo = ((MethodInfo)members[i]).GetParameters();
                            } else
                                throw new NotSupportedException("neither a method or property");

                            // Parameters count mismatch?
                            if (methodArgsInfo.Length != args.Length)
                                continue;
                            // Parameter type incompatibility?
                            bool compatibleArgs = true;

                            for (int j = 0; j < args.Length; j++) {
                                if (argsType[j] != methodArgsInfo[j].ParameterType) {
                                    compatibleArgs = false;
                                    break;
                                }
                            }

                            if (compatibleArgs == false)
                                continue;

                            bestMemberIndex = i;
                            break;
                        }
                    }

                    if (bestMemberIndex == -1)
                        throw new InvalidOperationException(String.Format("method or property {0} has an ambiguous definition", memberPattern));
                }

                // Method or indexed property
                memberInfo = members[bestMemberIndex];
                // Parse method arguments
                if (memberInfo.MemberType == MemberTypes.Property) {
                    methodArgsInfo = ((PropertyInfo)memberInfo).GetIndexParameters();
                    Debug.Assert((methodArgsInfo != null) && (methodArgsInfo.Length > 0));
                } else if (memberInfo.MemberType == MemberTypes.Method) {
                    methodArgsInfo = ((MethodInfo)memberInfo).GetParameters();
                } else
                    throw new NotSupportedException("neither a method or property");

                if (args.Length != methodArgsInfo.Length)
                    throw new InvalidOperationException("argument count mismatch");

                memberArgs = new object[args.Length];
                for (int i = 0; i < args.Length; i++) {
                    Type argType = methodArgsInfo[i].ParameterType;

                    if (argType == typeof(String)) {
                        memberArgs[i] = args[i].Substring(1, args[i].Length - 2);
                    } else if (argType == typeof(Int32)) {
                        memberArgs[i] = Int32.Parse(args[i]);
                    } else if (argType == typeof(UInt32)) {
                        memberArgs[i] = UInt32.Parse(args[i]);
                    } else if (argType == typeof(Single)) {
                        memberArgs[i] = Single.Parse(args[i]);
                    } else if (argType == typeof(Double)) {
                        memberArgs[i] = Double.Parse(args[i]);
                    } else if (argType == typeof(Int16)) {
                        memberArgs[i] = Int16.Parse(args[i]);
                    } else if (argType == typeof(UInt16)) {
                        memberArgs[i] = UInt16.Parse(args[i]);
                    } else if (argType == typeof(Char)) {
                        memberArgs[i] = Char.Parse(args[i]);
                    } else if (argType == typeof(Byte)) {
                        memberArgs[i] = Byte.Parse(args[i]);
                    } else
                        throw new InvalidOperationException(String.Format("argument of type {0} is not supported", argType.Name));
                }
            } else {
                MemberInfo[] members = objType.GetMember(memberPattern);

                if ((members == null) || (members.Length == 0))
                    throw new InvalidOperationException(String.Format("no property/field {0}", memberPattern));

                if (members.Length > 1) {
                    members = Array.FindAll<MemberInfo>(members, delegate(MemberInfo member) {
                        return (member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Field);
                    });
                }

                if (members.Length != 1)
                    throw new InvalidOperationException(String.Format("field of property {0} has an ambiguous definition", memberPattern));

                // Property of field
                memberInfo = members[0];
                // Not an indexed property
                memberArgs = null;
            }
        }

        /// <summary>
        /// Access to the object member.
        /// </summary>
        /// <param name="obj">
        /// A <see cref="System.Object"/> which type defines the underlying member.
        /// </param>
        /// <param name="memberInfo">
        /// A <see cref="System.Reflection.MemberInfo"/> that represent the member.
        /// </param>
        /// <param name="memberArgs">
        /// An array of <see cref="System.Object"/> that represent the argument list required for calling a method or an indexed
        /// property.
        /// </param>
        /// <returns></returns>
        private static object GetObjectMemberValue(object obj, MemberInfo memberInfo, object[] memberArgs)
        {
            if (memberInfo == null)
                throw new ArgumentNullException("memberInfo");

            // Get the value
            switch (memberInfo.MemberType) {
                case MemberTypes.Field: {
                        FieldInfo fieldInfo = (FieldInfo)memberInfo;

                        if (fieldInfo.FieldType.IsValueType) {
                            TypedReference typedReference = __makeref(obj);
                            return (fieldInfo.GetValueDirect(typedReference));
                        } else
                            return (fieldInfo.GetValue(obj));
                    }
                case MemberTypes.Property:
                    return (((PropertyInfo)memberInfo).GetValue(obj, memberArgs));
                case MemberTypes.Method:
                    return (((MethodInfo)memberInfo).Invoke(obj, memberArgs));
                default:
                    throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
            }
        }

        private static void SetObjectMemberValue(object obj, MemberInfo memberInfo, params object[] memberArgs)
        {
            if (memberInfo == null)
                throw new ArgumentNullException("memberInfo");

            // Set the value
            switch (memberInfo.MemberType) {
                case MemberTypes.Field: {
                        FieldInfo fieldInfo = (FieldInfo)memberInfo;

                        if (fieldInfo.FieldType.IsValueType) {
                            TypedReference typedReference = __makeref(obj);
                            fieldInfo.SetValueDirect(typedReference, memberArgs[0]);
                        } else
                            fieldInfo.SetValue(obj, memberArgs[0]);
                    } break;
                case MemberTypes.Property:
                    ((PropertyInfo)memberInfo).SetValue(obj, memberArgs[0], null);
                    break;
                case MemberTypes.Method:
                    ((MethodInfo)memberInfo).Invoke(obj, memberArgs);
                    break;
                default:
                    throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
            }
        }


        private static Type GuessMethodArgumentType(string methodArg)
        {
            if (String.IsNullOrEmpty(methodArg))
                throw new ArgumentNullException("methodArg");

            if (sMethodArgString.IsMatch(methodArg))
                return (typeof(String));



            return (null);
        }

        /// <summary>
        /// Regular expression used for matching method calls.
        /// </summary>
        private static readonly Regex sMethodRegex = new Regex(@"^(?<MethodName>\w+) *\( *(?<MethodArgs>.*) *\)$");

        /// <summary>
        /// Regular expression used for matching method string arguments.
        /// </summary>
        private static readonly Regex sMethodArgString = new Regex(@"\"".*\""");

        /// <summary>
        /// Regular expression used for matching collection indexer calls.
        /// </summary>
        private static readonly Regex sCollectionRegex = new Regex(@"^(?<MethodName>\w+) *\[ *(?<MethodArgs>.*) *\]$");

        #endregion
    }
}
4

2 に答える 2

3

__makerefは文書化されていないキーワードです。私はそれが以前に使用されたのを見たことがないので、それが何をしているのか正確にはわかりません。ただし、変更する前に値型をオブジェクトにキャストするだけで、__makerefが実行しようとしていると私が想定していることを実行できます。

JonSkeetがこの回答の詳細を説明しています

https://stackoverflow.com/a/6280540/141172

ちなみに、文書化されていないものには、時間の経過とともに変化する方法があります。私は本番コードをそれらに依存しません。

于 2012-07-29T17:31:27.917 に答える
1

objパラメータを変数として宣言するrefと、構造体を変更した後にパラメータを割り当てることができます。これは可変/変更可能な構造体ですか?

フィールドの型が値の型であるかどうかを確認する必要がある理由がわかりません。私たちはどこのケースについて話し合っていると思いましたobj.GetType().IsValueTypeか?

添加:

私はそれについて少し考えましたが、ref boxing を持っている場合、パラメーターを作成してもうまくいかないと思います。それも必要ないはずです。

あなたの問題は Set メソッドだけにあると思いますか? の使用が含まれていないようですSetObjectMemberValue。しかし、私はあなたがそれを次のように使いたいと思っていると思います:

var myMutableStruct = XXX;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
// use myMutableStruct with new field value

メソッドに渡すボックス化されたコピーであるため、これは構造体では機能しません。メソッドが何をするにしても、そのコピーにしかアクセスできません。代わりに、次のように言えます。

var myMutableStruct = XXX;
object boxToKeep = myMutableStruct;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
myMutableStruct = (MyMutableStruct)boxToKeep;
// use myMutableStruct with new field value

これが気に入らない場合は、メソッドを の型でジェネリックにしてみてくださいobj。署名はSetObjectMemberValue<TObj>(TObj obj, MemberInfo memberInfo, params object[] memberArgs). ジェネリック型の場合、ボックス化は発生しませんが、メソッド本体内での再割り当てを使用して、マジックを使用する__makeref 、パラメーターref(so ) を作成する必要がある可能性があります。ref TObj obj質問で自分自身をリンクしている Stack Overflow スレッドを参照してください。

于 2012-07-29T17:42:09.187 に答える