簡體   English   中英

反射和拳擊值類型

[英]Reflection and boxing value types

我正在編寫一個類,通過使用字符串模式,通過反射來獲取和設置對象的值。 即使在復雜的模式上,這個類也運行良好,但是我得到了不期望的行為,我不知道如何解決/解決方法。

本質上,當類訪問屬於值類型的字段或屬性時,一切正常,但它在值類型的副本上運行。 實際上,當我使用字符串模式設置值時,實際值類型不會更新。

該類保留了一個object引用和一個MemberInfo實例(這些對象是通過分析根對象上的訪問模式得到的); 通過這種方式,我可以從object實例開始獲取或設置MemberInfo指定的成員。

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
    }
}

__makeref是一個未記錄的關鍵字。 我以前從未見過它,所以不知道它到底在做什么。 但是,您可以通過在修改之前將值類型轉換為對象來完成我假設__makeref嘗試執行的操作。

Jon Skeet解釋了這個答案中的具體細節

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

另一方面, 未記錄的事物有一種隨時間變化的方式。 我不會依賴它們來生成代碼。

如果將obj參數聲明為ref變量,那么可能在更改結構后可以將其指定給它。 這是一個可變/可變的結構嗎?

我不確定為什么查看字段類型是否為值類型是相關的。 我以為我們正在討論obj.GetType().IsValueType

加成:

我已經考慮了一下, 如果你有拳擊 ,我不再認為它會起作用參數ref 它甚至不應該是必要的。

我認為你的問題只有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類型obj該方法通用。 然后簽名可以是SetObjectMemberValue<TObj>(TObj obj, MemberInfo memberInfo, params object[] memberArgs) 使用泛型類型時,不會發生裝箱,但是您可能需要使用__makeref的魔力或者在方法體內重新分配參數ref (所以ref TObj obj )。 請參閱您在問題中鏈接的Stack Overflow線程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM