I'm writing a class able to get and set values from an object by using a string pattern, by means of reflection. The class works well, even on complex patterns, but I got un expected behaviour that I don't know how to solve/workaround.
Essentially, when the class is accessing to a field or property that is a value type, everything works, but it operates on a copy of the value type. Indeed, when I was to set a value using a string pattern, the real value type is not being updated.
The class mantains an object reference and a MemberInfo instance (those objects are got by analysing the access pattern on a root object); in this way I can get or set the member specified by MemberInfo starting from the object instance.
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));
}
}
When the obj parameter is a struct value, it happens the error: I get/set from the boxed value.
How can I workaround this? I've already checked this question, but without success (you can see the code on field management): the boxing happens all the same since I assign the field value into a object variable.
The make things more clear, here is the complete code of class in 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
}
}
__makeref is an undocumented keyword. I have never seen it used before so don't know exactly what it is doing. However, you can accomplish what I assume __makeref is trying to do just by casting the value type to object before modifying.
Jon Skeet explains the specifics in this answer
https://stackoverflow.com/a/6280540/141172
On a side note, undocumented things have a way of changing over time. I would not rely on them for production code.
If you declare your obj parameter as a ref variable, then maybe you can assign back to it after you changed your struct. Is this a mutable/changable struct?
I'm not sure why it's relevant to see if the field type is a value type. I thought we were discussing the case where obj.GetType().IsValueType?
Addition:
I've thought a bit about it, and I no longer think it will work to make the parameter ref if you have boxing. It shouldn't even be necessary.
I think your problem is only with the Set method? It looks like you didn't include your use of SetObjectMemberValue. But I suspect you want to use it like this:
var myMutableStruct = XXX;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
// use myMutableStruct with new field value
This can never work with a struct, because it's a boxed copy you pass to the method. No matter what the method does, it has only access to that copy. Instead you could say:
var myMutableStruct = XXX;
object boxToKeep = myMutableStruct;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
myMutableStruct = (MyMutableStruct)boxToKeep;
// use myMutableStruct with new field value
If you don't like this, try making the method generic in the type of obj. The signature could then be SetObjectMemberValue<TObj>(TObj obj, MemberInfo memberInfo, params object[] memberArgs). With a generic type, no boxing occurs, but you will probably need to use the magic of __makeref or make the parameter ref (so ref TObj obj) with reassignment inside the method body. See the Stack Overflow thread you link yourself in your question.
Related
I need to copy the events from one UnityEvent to another, as once I figure this out I will be switching out the target at runtime to another object, what I have so far is:
MethodInfo info = UnityEventBase.GetValidMethodInfo (event1.GetPersistentTarget (i), event1.GetPersistentMethodName (i), Type.EmptyTypes);
UnityAction action = Delegate.CreateDelegate (typeof (UnityAction), info) as UnityAction;
event2.AddListener (action);
I get ArgumentNullException: Argument cannot be null., and if I change Type.EmptyTypes to new Type[] { typeof (float) }, I get ArgumentException: method argument length mismatch.
The problem being that I don't know what to put in there since I don't know what the type is (since Unity Events can send a bool, float, etc.)
Unity Docs don't cover this, so hopefully someone else has had success in the past.
This can be achieved using reflection and recursively copy every value field of the UnityEvent class. I wouldn't use this in runtime due to performance hits, but for editor stuff is very useful. I use one static helper class and an extension for cloning the list.
ReflectionHelper.cs
using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
namespace CEUtilities.Helpers
{
public static class ReflectionHelper
{
/// <summary>
/// Gets all fields from an object and its hierarchy inheritance.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="flags">The flags.</param>
/// <returns>All fields of the type.</returns>
public static List<FieldInfo> GetAllFields(this Type type, BindingFlags flags)
{
// Early exit if Object type
if (type == typeof(System.Object))
{
return new List<FieldInfo>();
}
// Recursive call
var fields = type.BaseType.GetAllFields(flags);
fields.AddRange(type.GetFields(flags | BindingFlags.DeclaredOnly));
return fields;
}
/// <summary>
/// Perform a deep copy of the class.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj">The object.</param>
/// <returns>A deep copy of obj.</returns>
/// <exception cref="System.ArgumentNullException">Object cannot be null</exception>
public static T DeepCopy<T>(T obj)
{
if (obj == null)
{
throw new ArgumentNullException("Object cannot be null");
}
return (T)DoCopy(obj);
}
/// <summary>
/// Does the copy.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentException">Unknown type</exception>
private static object DoCopy(object obj)
{
if (obj == null)
{
return null;
}
// Value type
var type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
// Array
else if (type.IsArray)
{
Type elementType = type.GetElementType();
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(DoCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
// Unity Object
else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
{
return obj;
}
// Class -> Recursion
else if (type.IsClass)
{
var copy = Activator.CreateInstance(obj.GetType());
var fields = type.GetAllFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
var fieldValue = field.GetValue(obj);
if (fieldValue != null)
{
field.SetValue(copy, DoCopy(fieldValue));
}
}
return copy;
}
// Fallback
else
{
throw new ArgumentException("Unknown type");
}
}
}
}
UnityEventExtension.cs
using UnityEngine.Events;
using CEUtilities.Helpers;
namespace UnityEngine
{
public static class UnityEventExtension
{
/// <summary>
/// Clones the specified unity event list.
/// </summary>
/// <param name="ev">The unity event.</param>
/// <returns>Cloned UnityEvent</returns>
public static T Clone<T>(this T ev) where T : UnityEventBase
{
return ReflectionHelper.DeepCopy(ev);
}
}
}
And then it can be use like this
this.OnStart = target.OnStart.Clone();
I know this is old but I spent most of the day scouring the internet to help me write something. I ended up coming up with this simple function you can use. This will only work in the editor (but then again when will you ever want to do this otherwise?).
As long as this is a UnityEvent it will copy unity events with their parameter values as well (Objects, strings, ints, floats, voids, and bools) from one target UnityEvent to another on a different or the same component.
Everyone is welcome to download it and build upon if you would like. Check it out here:
https://gist.github.com/wesleywh/1c56d880c0289371ea2dc47661a0cdaf
For anybody that stumbles across this in the future, this worked:
MethodInfo info = UnityEventBase.GetValidMethodInfo (event1.GetPersistentTarget (i), event1.GetPersistentMethodName (i), new Type[] { typeof (float) });
UnityAction execute = () => info.Invoke (event1.GetPersistentTarget (i), new object[] { 180f });
event2.AddListener (execute);
It just doesn't expose the copied listener in the inspector, so still on the hunt for a perfect solution.
I am using reflection in one of my C# projects: it is Portable Class Library targeting Windows 8.1 and Windows Phone 8.1.
In that project, I have an interface named IMyInterface that has a method DoSomething with a generic parameter TGenericObject. I also have a class named MyClass. At one point, I need to look up the method DoSomething in the specified interface by reflection. So, I am using the GetRuntimeMethod method from the Type class with the actual parameter's type, which is MyClass in my example.
Please, keep in mind that the example I am providing here is just to highlight the problem I am facing. The reality is that the interface IMyInterface and the class MyClass are in another project.
Here's the deal: I was expecting the GetRuntimeMethod to return the MethodInfo of the DoSomething method, but it did not: null is returned.
Is there something easy that I am missing to find the DoSomething method from the IMyInterface or do I have to get hands dirtier?
public interface IMyInterface
{
void DoSomething<TGenericObject>(TGenericObject myGenericObject);
}
public class MyClass
{ }
class Program
{
static void Main(string[] args)
{
MyClass myClassInst = new MyClass();
MethodInfo methodInfo = typeof (IMyInterface).GetRuntimeMethod("DoSomething", new [] { myClassInst.GetType() });
}
}
I was able to code my own extension method that actually do what I expected from GetRuntimeMethod method. What bothers me is that I still do not understand why the GetRuntimeMethod method provided by .NET returns null in my sample.
Here is the incomplete class that temporarily fixes my issue. This is a very naive approach but it is a starting point. There are a lot of things missing in that class but at least, it is an answer that allows me to go on.
public static class TypeExtensions
{
#region Public Methods
/// <summary>
/// Looks for the method in the type matching the name and arguments.
/// </summary>
/// <param name="type"></param>
/// <param name="methodName">
/// The name of the method to find.
/// </param>
/// <param name="args">
/// The types of the method's arguments to match.
/// </param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">
/// Thrown if:
/// - The name of the method is not specified.
/// </exception>
public static MethodInfo GetRuntimeMethod(this Type type, string methodName, Type[] args)
{
if (ReferenceEquals(type, null))
throw new NullReferenceException("The type has not been specified.");
if (string.IsNullOrEmpty(methodName))
throw new ArgumentNullException("methodName", "The name of the method has not been specified.");
var methods = type.GetRuntimeMethods().Where(methodInfo => string.Equals(methodInfo.Name, methodName, StringComparison.OrdinalIgnoreCase)).ToList();
if (!methods.Any())
return null; // No methods have the specified name.
if (methods.Count == 1)
{
MethodInfo methodInfo = methods.Single();
return IsSignatureMatch(methodInfo, args) ? methodInfo : null;
}
// Oh noes, don't make me go there.
throw new NotImplementedException("Resolving overloaded methods is not implemented as of now.");
}
#endregion
#region Private Methods
/// <summary>
/// Finds out if the provided arguments matches the specified method's signature.
/// </summary>
/// <param name="methodInfo"></param>
/// <param name="args"></param>
/// <returns></returns>
private static bool IsSignatureMatch(MethodBase methodInfo, Type[] args)
{
Debug.Assert(!ReferenceEquals(methodInfo, null), "The methodInfo has not been specified.");
// Gets the parameters of the method to analyze.
ParameterInfo[] parameters = methodInfo.GetParameters();
int currentArgId = 0;
foreach (ParameterInfo parameterInfo in parameters)
{
if (!ReferenceEquals(args, null) && currentArgId < args.Length)
{
// Find out if the types matchs.
if (parameterInfo.ParameterType == args[currentArgId])
{
currentArgId++;
continue; // Yeah! Try the next one.
}
// Is this a generic parameter?
if (parameterInfo.ParameterType.IsGenericParameter)
{
// Gets the base type of the generic parameter.
Type baseType = parameterInfo.ParameterType.GetTypeInfo().BaseType;
// TODO: This is not good v and works with the most simple situation.
// Does the base type match?
if (args[currentArgId].GetTypeInfo().BaseType == baseType)
{
currentArgId++;
continue; // Yeah! Go on to the next parameter.
}
}
}
// Is this parameter optional or does it have a default value?
if (parameterInfo.IsOptional || parameterInfo.HasDefaultValue)
continue; // Uhum. So let's ignore this parameter for now.
// No need to go further. It does not match :(
return false;
}
// Ye!
return true;
}
#endregion
}
In .net standard (performance aside):
public static MethodInfo ResolveMethod(this Type objType, string methodName, Type[] parameterTypes)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
List<MethodBase> regularMethods = new List<MethodBase>();
List<MethodBase> genericMethods = new List<MethodBase>();
foreach (MethodInfo methodInfo in objType.GetRuntimeMethods())
{
if (methodInfo.Name == methodName)
{
if (methodInfo.GetParameters().Length == parameterTypes.Length)
{
if (methodInfo.IsGenericMethod)
genericMethods.Add(methodInfo);
else
regularMethods.Add(methodInfo);
}
}
}
MethodInfo found = null;
if (regularMethods.Count > 0)
{
MethodBase[] regulaMethodsArray = regularMethods.ToArray();
found = Type.DefaultBinder.SelectMethod(flags, regulaMethodsArray, parameterTypes, null) as MethodInfo;
}
if (found == null)
{
MethodBase[] genericMethodsArray = genericMethods.ToArray();
foreach (MethodInfo method in genericMethods)
{
var templateTypes = GetTemplate(parameterTypes, method, out int genericCount);
found = Type.DefaultBinder.SelectMethod(flags, genericMethodsArray, templateTypes, null) as MethodInfo;
if (found != null)
{
found = found.MakeGenericMethod(GetReplacements(parameterTypes, templateTypes, genericCount));
break;
}
}
}
return found;
}
public static Type[] GetReplacements(Type[] parameterTypes, Type[] template, int genericCount)
{
Type[] result = new Type[genericCount];
int p = 0;
for (int i = 0; i < parameterTypes.Length; i++)
{
if (template[i].IsGenericMethodParameter)
{
result[p] = parameterTypes[p];
p++;
}
}
return result;
}
public static Type[] GetTemplate(Type[] parameterTypes, MethodInfo methodInfo, out int genericCount)
{
genericCount = 0;
Type[] result = new Type[parameterTypes.Length];
ParameterInfo[] p = methodInfo.GetParameters();
for (int i = 0; i < parameterTypes.Length; i++)
{
if (p[i].ParameterType.IsGenericParameter)
{
result[i] = Type.MakeGenericMethodParameter(i);
genericCount++;
}
else
{
result[i] = parameterTypes[i];
}
}
return result;
}
}
I am trying to get a static field info into metro app and I don't find a way to do that.
I have tried:
- type.GetRuntimeField
- typeInfo.GetDeclaredField in a loop to delve into every parent types
/// <summary>
/// Gets the field info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="fieldName">The name of the field</param>
/// <returns>The field info if found, null otherwise</returns>
public static FieldInfo GetField(this Type type, string fieldName)
{
var currentType = type;
FieldInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredField(fieldName);
currentType = typeInfo.BaseType;
}
return result;
}
... am I missing something or is there anyway to get a static field on a type using reflection in metro app?....
edit:
Well, I am so sorry for those who have waste time on this question, Dependency properties defined in the framework are actualy not readonly static fields, they are static properties... As I usualy declare my dps as field, I didn't consider the fact that form example FrameworkElement.Width could be a property...
So here is the code I used to get fields and property info:
public static class TypeExtensions
{
/// <summary>
/// Gets the field info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="fieldName">The name of the field</param>
/// <returns>The field info if found, null otherwise</returns>
public static FieldInfo GetField(this Type type, string fieldName)
{
var currentType = type;
FieldInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredField(fieldName);
currentType = typeInfo.BaseType;
}
return result;
}
/// <summary>
/// Gets the property info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The field info if found, null otherwise</returns>
public static PropertyInfo GetProperty(this Type type, string propertyName)
{
var currentType = type;
PropertyInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredProperty(propertyName);
currentType = typeInfo.BaseType;
}
return result;
}
}
public static class DependencyObjectExtensions
{
public static DependencyProperty GetDependencyProperty(this DependencyObject dependencyObject, string propertyName)
{
var dependencyPropertyName = propertyName + "Property";
var type = dependencyObject.GetType();
var fieldInfo = type.GetField(dependencyPropertyName);
if (fieldInfo == null)
{
var propertyInfo = type.GetProperty(dependencyPropertyName);
if (propertyInfo != null)
{
return propertyInfo.GetValue(dependencyObject) as DependencyProperty;
}
}
else
{
var value = fieldInfo.GetValue(dependencyObject);
return value as DependencyProperty;
}
return null;
}
}
Thanks a lot
Regards,
Charles
The database I am working with currently has a varchar field, and in my code I want to map the potential values to a enumeration like:
public enum UserStatus
{
Anonymous,
Enrolled,
SuperUser
}
At the database level for this column, there is a constrain on it where the value has to be:
ANONYMOUS
ENROLLED
SUPERUSER
Is it possible for me to do:
UserStatus.SuperUser.ToString()
And have that value be SUPERUSER, and this be consistant and not screw up down the road?
A better solution may be to take advantage of the DescriptionAttribute:
public enum UserStatus
{
[Description("ANONYMOUS")]
Anonymous,
[Description("ENROLLED")]
Enrolled,
[Description("SUPERUSER")]
SuperUser
}
Then use something like:
/// <summary>
/// Class EnumExtenions
/// </summary>
public static class EnumExtenions
{
/// <summary>
/// Gets the description.
/// </summary>
/// <param name="e">The e.</param>
/// <returns>String.</returns>
public static String GetDescription(this Enum e)
{
String enumAsString = e.ToString();
Type type = e.GetType();
MemberInfo[] members = type.GetMember(enumAsString);
if (members != null && members.Length > 0)
{
Object[] attributes = members[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
enumAsString = ((DescriptionAttribute)attributes[0]).Description;
}
}
return enumAsString;
}
/// <summary>
/// Gets an enum from its description.
/// </summary>
/// <typeparam name="TEnum">The type of the T enum.</typeparam>
/// <param name="description">The description.</param>
/// <returns>Matching enum value.</returns>
/// <exception cref="System.InvalidOperationException"></exception>
public static TEnum GetFromDescription<TEnum>(String description)
where TEnum : struct, IConvertible // http://stackoverflow.com/a/79903/298053
{
if (!typeof(TEnum).IsEnum)
{
throw new InvalidOperationException();
}
foreach (FieldInfo field in typeof(TEnum).GetFields())
{
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == description)
{
return (TEnum)field.GetValue(null);
}
}
else
{
if (field.Name == description)
{
return (TEnum)field.GetValue(null);
}
}
}
return default(TEnum);
}
}
So now you're referencing UserStatus.Anonymous.GetDescription().
Of course you could always make your own DatabaseMapAttribute (or what-have-you) and create your own extension methods. Then you can kill a reference to System.ComponentModel. Completely your call.
You can't override ToString for enums, instead you can create your own Extension Method like:
public static class MyExtensions
{
public static string ToUpperString(this UserStatus userStatus)
{
return userStatus.ToString().ToUpper();// OR .ToUpperInvariant
}
}
And then call it like:
string str = UserStatus.Anonymous.ToUpperString();
Enum.ToString supports 4 different formats. I'd go for:
UserStatus.SuperUser.ToString("G").ToUpper();
"G" ensures that it will try first to get the string representation of your enum.
This is my method:
/// <summary>
/// Uses Dictionary(Key,Value) where key is the property and value is the field name.
/// Matches the dictionary of mandatory fields with object properties
/// and checks whether the current object has values in it or
/// not.
/// </summary>
/// <param name="mandatoryFields">List of string - properties</param>
/// <param name="o">object of the current class</
/// <param name="message">holds the message for end user to display</param>
/// <returns>The name of the property</returns>
public static bool CheckMandatoryFields(Dictionary<string,string > mandatoryFields, object o,out StringBuilder message)
{
message = new StringBuilder();
if(mandatoryFields !=null && mandatoryFields.Count>0)
{
var sourceType = o.GetType();
var properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Static);
for (var i = 0; i < properties.Length; i++)
{
if (mandatoryFields.Keys.Contains(properties[i].Name))
{
if (string.IsNullOrEmpty( properties[i].GetValue(o, null).ToString()))
{
message.AppendLine(string.Format("{0} name is blank.", mandatoryFields.Values));
}
}
}
if(message.ToString().Trim().Length>0)
{
return false;
}
}
return true;
}
In this I have params Dictionary which will hold the property name of the class and its corresponding fieldname from the UI(manually fed by developer in the business layer or UI).
So what I want is that when the property is on the way to validate, if the property is found null or blank, then its corresponding fieldname, which is actually the value of the dictionary will get added to the stringbuilder message in the method above.
I hope i am clear.
Do the loop the other way:
public static bool CheckMandatoryFields(Dictionary<string,string > mandatoryFields, object o,out StringBuilder message)
{
message = new StringBuilder();
if(mandatoryFields == null || mandatoryFields.Count == 0)
{
return true;
}
var sourceType = o.GetType();
foreach (var mandatoryField in mandatoryFields) {
var property = sourceType.GetProperty(mandatoryField.Key, BindingFlags.Public | BindingFlags.Static);
if (property == null) {
continue;
}
if (string.IsNullOrEmpty(property.GetValue(o, null).ToString()))
{
message.AppendLine(string.Format("{0} name is blank.", mandatoryField.Value));
}
}
return message.ToString().Trim().Length == 0;
}
This way you iterate over the properties you want to check, so you always have a handle on the "current" property and know the corresponding key and value from the dictionary.
The snippet
if (property == null) {
continue;
}
causes the function to treat properties that exist as names in the dictionary but not as actual properties on the type to be treated as valid, to mirror what your original code does.