2012-07-29 91 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