2012-07-29 93 views
3

我在写一个类,它能够通过使用字符串模式(通过反射)从对象中获取和设置值。即使在复杂模式下,该类也能很好地工作,但是我得到了不可预料的行为,我不知道如何解决/解决方法。反射和拳击值类型

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

该类包含一个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参数是一个结构值,它发生的错误:我获得/从装箱值设定。

我该如何解决这个问题?我已经检查了这个question,但没有成功(你可以看到现场管理的代码):因为我将字段值赋给一个对象变量,所以拳击发生的情况完全相同。

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

// 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 
    } 
} 
+0

如何使这两种方法是通用的?这样,你可以避免拳击/拆箱。 – 2012-07-29 17:28:01

+0

我不知道对象的类型,我只对对象实例进行操作。 – Luca 2012-07-29 17:32:10

+1

会传递你的对象作为'ref'参数的帮助吗? – 2012-07-29 17:34:05

回答

2

__makeref是一个无证的关键字。我从来没有见过它使用过,所以不知道它在做什么。但是,您可以在修改之前完成我假设__makeref正在尝试执行的操作,只需将值类型转换为对象即可。

乔恩斯基特解释的细节在这个答案

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

在一个侧面说明,无证东西都随时间变化的方式。我不会依赖他们的生产代码。

+0

即使使用* makeref *,我的课程也无法正常工作。 – Luca 2012-07-29 17:48:54

+0

您是否尝试将它转换为* object *,正如Jon在我链接的答案中所显示的那样? – 2012-07-29 18:15:23

+0

只有当我可以有一个左值... – Luca 2012-07-29 18:19:27

1

如果您将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类型通用的。签名可以是SetObjectMemberValue<TObj>(TObj obj, MemberInfo memberInfo, params object[] memberArgs)。使用通用类型时,不会出现拳击,但您可能需要使用魔术 在方法体内重新分配参数ref(so ref TObj obj)。请参阅您在问题中链接自己的堆栈溢出线程。

+0

我没有得到* ref *参数如何在我的解决方案中工作(请参阅有问题的代码)。你能详细说明吗? – Luca 2012-07-29 18:00:07

+0

@Luca我阐述了(见上)。 – 2012-07-29 21:18:54