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.
Related
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.
I have the below in my controller and would like to pass x, y, and z of type float to subscribers. I'm having some difficulties catching on however. What would I have to adjust to allow me to pass my floats (x, y, z) as parameters? Thank you.
private void ProcessEvents()
{
if(Input.GetMouseButtonDown(1))
{
// Data to be passed to subscribers
float x = Input.mousePosition.x;
float y = Input.mousePosition.y;
float z = Input.mousePosition.z;
var publisher = new EventPublisher();
var handler = new Handler();
// Void delegate with one parameter
string eventName = "RightClickEvent";
var rightClickEvent = publisher.GetType().GetEvent(eventName);
rightClickEvent.AddEventHandler(publisher, EventProxy.Create<int>(rightClickEvent, i=>Debug.LogError(i + "!")));
publisher.PublishEvents();
}
}
Other sources:
public class ExampleEventArgs : EventArgs
{
public int IntArg {get; set;}
}
Event Publisher:
public class EventPublisher
{
public event EventHandler<ExampleEventArgs> RightClickEvent;
/// <summary>
/// Publishes the events.
/// </summary>
public void PublishEvents()
{
if(RightClickEvent != null)
{
RightClickEvent(this, new ExampleEventArgs{IntArg = 5});
}
}
}
Handler:
public class Handler
{
public void HandleEventWithArg(int arg) { Debug.LogError("Arg: " + string.Format("[{0}]", arg)); }
}
EventProxy:
static class EventProxy
{
// Void delegate with one parameter
static public Delegate Create<T>(EventInfo evt, Action<T> d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg)
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray();
var arg = getArgExpression(parameters[1], typeof(T));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg);
var lambda = Expression.Lambda(body,parameters);
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
// Returns an expression that represents an argument to be passed to the delegate
static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
{
if(eventArgs.Type == typeof(ExampleEventArgs) && handlerArgType == typeof(int))
{
//"x1.IntArg"
var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
return Expression.MakeMemberAccess(eventArgs,memberInfo);
}
throw new NotSupportedException(eventArgs + "->" + handlerArgType);
}
// Void delegates with no parameters
static public Delegate Create(EventInfo evt, Action d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, EventArgs x1) => d()
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x"));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"));
var lambda = Expression.Lambda(body,parameters.ToArray());
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
}
Ok, so i read what you said about decoupling application modules in MVC-like style. I normally like to work with strong typed code, even when using reflection, but i'm relatively new to MVC, and don't know the recommended practices. You know your requirements better than i do, so i simply edited Nguyen's solution, because i believe he was using some extensions that were not included in the file, and posted the result here. All credit goes to Nguyen.
namespace Dynamics
{
public static class DynamicHandler
{
/// <summary>
/// Invokes a static delegate using supplied parameters.
/// </summary>
/// <param name="targetType">The type where the delegate belongs to.</param>
/// <param name="delegateName">The field name of the delegate.</param>
/// <param name="parameters">The parameters used to invoke the delegate.</param>
/// <returns>The return value of the invocation.</returns>
public static object InvokeDelegate(this Type targetType, string delegateName, params object[] parameters)
{
return ((Delegate)targetType.GetField(delegateName).GetValue(null)).DynamicInvoke(parameters);
}
/// <summary>
/// Invokes an instance delegate using supplied parameters.
/// </summary>
/// <param name="target">The object where the delegate belongs to.</param>
/// <param name="delegateName">The field name of the delegate.</param>
/// <param name="parameters">The parameters used to invoke the delegate.</param>
/// <returns>The return value of the invocation.</returns>
public static object InvokeDelegate(this object target, string delegateName, params object[] parameters)
{
return ((Delegate)target.GetType().GetField(delegateName).GetValue(target)).DynamicInvoke(parameters);
}
/// <summary>
/// Adds a dynamic handler for a static delegate.
/// </summary>
/// <param name="targetType">The type where the delegate belongs to.</param>
/// <param name="fieldName">The field name of the delegate.</param>
/// <param name="func">The function which will be invoked whenever the delegate is invoked.</param>
/// <returns>The return value of the invocation.</returns>
public static Type AddHandler(this Type targetType, string fieldName,
Func<object[], object> func)
{
return InternalAddHandler(targetType, fieldName, func, null, false);
}
/// <summary>
/// Adds a dynamic handler for an instance delegate.
/// </summary>
/// <param name="target">The object where the delegate belongs to.</param>
/// <param name="fieldName">The field name of the delegate.</param>
/// <param name="func">The function which will be invoked whenever the delegate is invoked.</param>
/// <returns>The return value of the invocation.</returns>
public static Type AddHandler(this object target, string fieldName,
Func<object[], object> func)
{
return InternalAddHandler(target.GetType(), fieldName, func, target, false);
}
/// <summary>
/// Assigns a dynamic handler for a static delegate or event.
/// </summary>
/// <param name="targetType">The type where the delegate or event belongs to.</param>
/// <param name="fieldName">The field name of the delegate or event.</param>
/// <param name="func">The function which will be invoked whenever the delegate or event is fired.</param>
/// <returns>The return value of the invocation.</returns>
public static Type AssignHandler(this Type targetType, string fieldName,
Func<object[], object> func)
{
return InternalAddHandler(targetType, fieldName, func, null, true);
}
/// <summary>
/// Assigns a dynamic handler for a static delegate or event.
/// </summary>
/// <param name="target">The object where the delegate or event belongs to.</param>
/// <param name="fieldName">The field name of the delegate or event.</param>
/// <param name="func">The function which will be invoked whenever the delegate or event is fired.</param>
/// <returns>The return value of the invocation.</returns>
public static Type AssignHandler(this object target, string fieldName, Func<object[], object> func)
{
return InternalAddHandler(target.GetType(), fieldName, func, target, true);
}
private static Type InternalAddHandler(Type targetType, string fieldName,
Func<object[], object> func, object target, bool assignHandler)
{
Type delegateType;
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic |
(target == null ? BindingFlags.Static : BindingFlags.Instance);
var eventInfo = targetType.GetEvent(fieldName, bindingFlags);
if (eventInfo != null && assignHandler)
throw new ArgumentException("Event can be assigned. Use AddHandler() overloads instead.");
if (eventInfo != null)
{
delegateType = eventInfo.EventHandlerType;
var dynamicHandler = BuildDynamicHandler(delegateType, func);
eventInfo.GetAddMethod(true).Invoke(target, new Object[] { dynamicHandler });
}
else
{
var fieldInfo = targetType.GetField(fieldName);
//,target == null ? BindingFlags.Static : BindingFlags.Instance);
delegateType = fieldInfo.FieldType;
var dynamicHandler = BuildDynamicHandler(delegateType, func);
var field = assignHandler ? null : target == null
? (Delegate)fieldInfo.GetValue(null)
: (Delegate)fieldInfo.GetValue(target);
field = field == null
? dynamicHandler
: Delegate.Combine(field, dynamicHandler);
if (target != null)
target.GetType().GetField(fieldName).SetValue(target, field);
else
targetType.GetField(fieldName).SetValue(null, field);
//(target ?? targetType).SetFieldValue(fieldName, field);
}
return delegateType;
}
/// <summary>
/// Dynamically generates code for a method whose can be used to handle a delegate of type
/// <paramref name="delegateType"/>. The generated method will forward the call to the
/// supplied <paramref name="func"/>.
/// </summary>
/// <param name="delegateType">The delegate type whose dynamic handler is to be built.</param>
/// <param name="func">The function which will be forwarded the call whenever the generated
/// handler is invoked.</param>
/// <returns></returns>
public static Delegate BuildDynamicHandler(this Type delegateType, Func<object[], object> func)
{
var invokeMethod = delegateType.GetMethod("Invoke");
var parameters = invokeMethod.GetParameters().Select(parm =>
Expression.Parameter(parm.ParameterType, parm.Name)).ToArray();
var instance = func.Target == null ? null : Expression.Constant(func.Target);
var convertedParameters = parameters.Select(parm => Expression.Convert(parm, typeof(object))).Cast<Expression>().ToArray();
var call = Expression.Call(instance, func.Method, Expression.NewArrayInit(typeof(object), convertedParameters));
var body = invokeMethod.ReturnType == typeof(void)
? (Expression)call
: Expression.Convert(call, invokeMethod.ReturnType);
var expr = Expression.Lambda(delegateType, body, parameters);
return expr.Compile();
}
}
}
And i also added some code to test the methods, i could have just used simple lambdas when i assigned the callback delegates, but i rather use the "return true" definitions because i set breakpoints to check that the functions are actually called.
class TestClass
{
private void Test()
{
TestInstance();
TestStatic();
}
private void TestInstance()
{
var eventClass = new EventClass();
eventClass.InvokeDelegate("InstanceEventRaiseDelegate");
eventClass.AddHandler("InstanceEvent", parameters =>
{
return true;
});
eventClass.AddHandler("InstanceEventRaiseDelegate", parameters =>
{
return true;
});
eventClass.InvokeDelegate("InstanceEventRaiseDelegate");
eventClass.AssignHandler("InstanceEventRaiseDelegate", parameters =>
{
return true;
});
eventClass.InvokeDelegate("InstanceEventRaiseDelegate");
}
private void TestStatic()
{
typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
typeof(EventClass).AddHandler("StaticEvent", parameters =>
{
return true;
});
typeof(EventClass).AddHandler("StaticEventRaiseDelegate", parameters =>
{
return true;
});
typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
typeof(EventClass).AssignHandler("StaticEventRaiseDelegate", parameters =>
{
return true;
});
typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
}
public class EventClass
{
#region Instance
public EventClass()
{
InstanceEventRaiseDelegate = OnInstanceEvent;
}
public Action InstanceEventRaiseDelegate;
public event EventHandler InstanceEvent;
public void OnInstanceEvent()
{
if (InstanceEvent != null)
InstanceEvent(this, EventArgs.Empty);
}
#endregion
#region Static
static EventClass()
{
StaticEventRaiseDelegate = OnStaticEvent;
}
public static Action StaticEventRaiseDelegate;
public static event EventHandler StaticEvent;
public static void OnStaticEvent()
{
if (StaticEvent != null)
StaticEvent(null, EventArgs.Empty);
}
#endregion
}
}
Sorry for the late response, but it seems you were able to find the solution elsewhere :), goodluck.
I have added some code that will achieve what u require:
I modified the getArgExpression function to this
// Returns a List of expressions that represent the arguments to be passed to the delegate
static IEnumerable<Expression> getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
{
if (eventArgs.Type == typeof(ExampleEventArgs) && handlerArgType == typeof(int))
{
//"x1.IntArg"
var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
return new List<Expression> { Expression.MakeMemberAccess(eventArgs, memberInfo) };
}
if (eventArgs.Type == typeof(MousePositionEventArgs) && handlerArgType == typeof(float))
{
//"x1.X"
var xMemberInfo = eventArgs.Type.GetMember("X")[0];
//"x1.Y"
var yMemberInfo = eventArgs.Type.GetMember("Y")[0];
//"x1.Z"
var zMemberInfo = eventArgs.Type.GetMember("Z")[0];
return new List<Expression>
{
Expression.MakeMemberAccess(eventArgs, xMemberInfo),
Expression.MakeMemberAccess(eventArgs, yMemberInfo),
Expression.MakeMemberAccess(eventArgs, zMemberInfo),
};
}
throw new NotSupportedException(eventArgs + "->" + handlerArgType);
}
The calling function stays the same, because it has an overload for IEnumerable<Expression>.
I then added the EventArgs specific to your MousePosition situation
public class MousePositionEventArgs : EventArgs
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
}
Further more i wrote a new function in 'EventProxy' that will handle a delegate with 3 parameters of the same type
// Void delegate with three parameters
static public Delegate Create<T>(EventInfo eventInformation, Action<T, T, T> lambdaDelegate)
{
var handlerType = eventInformation.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, ExampleEventArgs x1) => d(x1.X,x1.Y,x1.Z)
var parameters = eventParams.Select(p => Expression.Parameter(p.ParameterType, "x")).ToArray();
var arg = getArgExpression(parameters[1], typeof(T));
var body = Expression.Call(Expression.Constant(lambdaDelegate), lambdaDelegate.GetType().GetMethod("Invoke"), arg);
var lambda = Expression.Lambda(body, parameters);
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
Now we can easily add a MouseEvent subscription by using the following code
rightClickEvent.AddEventHandler(publisher, EventProxy.Create<float>(rightClickEvent, (t, u, v) => Console.Write(t + "x" + u + "x" + v + "!")));
Design-wise this solution is not the best, it uses the current design you have shown, but i do have strong reservations as to maintaining a scalable and easy to follow architecture with this approach.
I would suggest you create an adapter/converter that will generate the lambda parameters from the EventArgs class, they can be easily specified in the EventArgs by implementing an interface, that way the getArgExpression will only use the converter to generate the appropriate parameters, and you can get rid of the many if (Type == typeof(...)).
It will take me some more time to draw up that solution, but if you are really interested, i can try to write it down, and post it.
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.