3

我正在编写一个类,该类能够通过反射使用字符串模式从对象获取和设置值。该课程运行良好,即使在复杂的模式上,但我得到了我不知道如何解决/解决方法的意外行为。

本质上,当类访问值类型的字段或属性时,一切正常,但它对值类型的副本进行操作。事实上,当我要使用字符串模式设置值时,实际值类型并没有被更新。

该类包含一个object引用和一个MemberInfo实例(这些对象是通过分析根对象上的访问模式得到的);这样我可以MemberInfoobject实例开始获取或设置指定的成员。

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参数是结构值时,会发生错误:我从装箱值中获取/设置。

我该如何解决这个问题?我已经检查了这个问题,但没有成功(你可以看到关于字段管理的代码):装箱的发生都是一样的,因为我将字段值分配给了一个对象变量。

让事情更清楚,这里是有问题的类的完整代码:

// 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 是一个未记录的关键字。我以前从未见过它使用过,所以不知道它在做什么。但是,您可以通过在修改之前将值类型转换为object来完成我认为 __makeref 尝试做的事情。

Jon Skeet 解释了这个答案中的细节

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 回答