我正在编写一个类,该类能够通过反射使用字符串模式从对象获取和设置值。该课程运行良好,即使在复杂的模式上,但我得到了我不知道如何解决/解决方法的意外行为。
本质上,当类访问值类型的字段或属性时,一切正常,但它对值类型的副本进行操作。事实上,当我要使用字符串模式设置值时,实际值类型并没有被更新。
该类包含一个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
参数是结构值时,会发生错误:我从装箱值中获取/设置。
我该如何解决这个问题?我已经检查了这个问题,但没有成功(你可以看到关于字段管理的代码):装箱的发生都是一样的,因为我将字段值分配给了一个对象变量。
让事情更清楚,这里是有问题的类的完整代码:
// 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
}
}