1

I'm trying to make a generic Mediator system, and want to allow messages with implicit operators to receive messages even if their respective type T is not provided.

This is where things are breaking, and I don't understand. Hoping someone smarter than me will.

public static class Mediator
{
    private static Dictionary<string, HashSet<Type>> m_typedMessages =
        new Dictionary<string, HashSet<Type>>();

    public static void NotifySubscribers<T>(string a_message, T a_arg, bool a_holdMessage = false)
    {
        TryAddTypedMessage(a_message, typeof(T));

        foreach (Type type in m_typedMessages[a_message])
        {
            if (type == typeof(T) ||  HasImplicitConversion(type, typeof(T)))
            {
                Type thisType = typeof(Catalogue<>).MakeGenericType(new Type[] { type });
                MethodInfo typedMethod = thisType.GetMethod("NotifySubscribers");
                typedMethod.Invoke(null, new object[] { a_message, a_arg, a_holdMessage });
            }
        }
    }

    private static bool HasImplicitConversion(Type a_baseType, Type a_targetType)
    {
        MethodInfo[] methods = a_baseType.GetMethods(BindingFlags.Public | BindingFlags.Static);
        IEnumerable<MethodInfo> implicitCasts = methods.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == a_baseType);
        bool hasMatchingCast = implicitCasts.Any(mi =>
        {
            ParameterInfo pi = mi.GetParameters().FirstOrDefault();
            return pi != null && pi.ParameterType == a_targetType;
        });

        return hasMatchingCast;
    }

HasImplicitConversion works fine. For my test, I have a class I made a class called ColorData, and it looks like this:

public class ColorData
{
    public float r, g, b, a;

    public static implicit operator ColorData(float a_float)
    {
        ColorData cd = new ColorData
        {
            r = a_float,
            g = 2f * a_float,
            b = a_float / 3f,
            a = 1f
        };
        return cd;
    }
}

It all goes good, and it passes all the checks until it gets to the typedMethod.Invoke, where despite having a implicit cast from float to ColorData, it still throws this:

ArgumentException: Object of type 'System.Single' cannot be converted to type 'SubTestUI+ColorData'. System.RuntimeType.CheckValue (System.Object value, System.Reflection.Binder binder, System.Globalization.CultureInfo culture, System.Reflection.BindingFlags invokeAttr) (at <1f0c1ef1ad524c38bbc5536809c46b48>:0) System.Reflection.MonoMethod.ConvertValues (System.Reflection.Binder binder, System.Object[] args, System.Reflection.ParameterInfo[] pinfo, System.Globalization.CultureInfo culture, System.Reflection.BindingFlags invokeAttr) (at <1f0c1ef1ad524c38bbc5536809c46b48>:0) System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <1f0c1ef1ad524c38bbc5536809c46b48>:0) System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <1f0c1ef1ad524c38bbc5536809c46b48>:0) Mouledoux.Mediation.Systems.Mediator.NotifySubscribers[T] (System.String a_message, T a_arg, System.Boolean a_holdMessage)

I assume it's because I still just pass a_arg as a type T when I call invoke, but shouldn't having an implicit operation just allow it to go through? I can't cast it to type 'type' which in this case IS ColorData at the time the error happens, but I can't cast a_arg as a Type. Any help at all appreciated.

1 Answers1

0

I got it. For anyone else curious, here is the new code:

public static class Mediator
{
    private static Dictionary<string, HashSet<Type>> m_typedMessages =
        new Dictionary<string, HashSet<Type>>();

    public static void NotifySubscribers<T>(string a_message, T a_arg, bool a_holdMessage = false)
    {
        TryAddTypedMessage(a_message, typeof(T));

        foreach (Type type in m_typedMessages[a_message])
        {
            if (type == typeof(T) |  HasImplicitConversion(type, typeof(T), out MethodInfo o_implicit))
            {
                Type thisType = typeof(Catalogue<>).MakeGenericType(new Type[] { type });
                MethodInfo typedMethod = thisType.GetMethod("NotifySubscribers");

                dynamic arg = o_implicit == null ? a_arg : o_implicit.Invoke(null, new object[] { a_arg });
                typedMethod.Invoke(null, new object[] { a_message, arg, a_holdMessage });
            }
        }
    }

    private static bool HasImplicitConversion(Type a_baseType, Type a_targetType, out MethodInfo o_mi)
    {
        MethodInfo[] methods = a_baseType.GetMethods(BindingFlags.Public | BindingFlags.Static);
        IEnumerable<MethodInfo> implicitCasts = methods.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == a_baseType);
        bool hasMatchingCast = implicitCasts.Any(mi =>
        {
            ParameterInfo pi = mi.GetParameters().FirstOrDefault();
            return pi != null && pi.ParameterType == a_targetType;
        });

        o_mi = implicitCasts.FirstOrDefault();
        return hasMatchingCast;
    }

Since I KNOW the message had an implicit method, I just skirt around the whole thing by 'out-ing' that implicit when I'm checking for it. Now that I have that method, I can just use it to convert a_arg from a type T, to a dynamic via the out implicit.