Problems with Rx EventMessager [Solved]
-
Im trying to publish a message that should communicate between classes:
public sealed class EventMessager : IEventMessager { private readonly Dictionary subscriberLookup = new Dictionary(); public IObservable Observe() { if (!subscriberLookup.TryGetValue(typeof(T), out object subject)) { subject = new Subject(); subscriberLookup.Add(typeof(T), subject); } return ((ISubject)subject).AsObservable(); } public void Publish(T @event) { if (subscriberLookup.TryGetValue(@event.GetType(), out object subject)) { ((Subject)subject).OnNext(@event); } } }
This works well, except for the time I wanted to use Reflection in order to initate a new Message class like so:
eventMessager.Publish(Activator.CreateInstance(w[0]));
It complains that it cannot cast the subject of class type that w is as an Subject Object. It will work as expected if I just say new MyClass instead of the activator. I really cant find any solutions online either, so does anyone know what is going on here?
-
Im trying to publish a message that should communicate between classes:
public sealed class EventMessager : IEventMessager { private readonly Dictionary subscriberLookup = new Dictionary(); public IObservable Observe() { if (!subscriberLookup.TryGetValue(typeof(T), out object subject)) { subject = new Subject(); subscriberLookup.Add(typeof(T), subject); } return ((ISubject)subject).AsObservable(); } public void Publish(T @event) { if (subscriberLookup.TryGetValue(@event.GetType(), out object subject)) { ((Subject)subject).OnNext(@event); } } }
This works well, except for the time I wanted to use Reflection in order to initate a new Message class like so:
eventMessager.Publish(Activator.CreateInstance(w[0]));
It complains that it cannot cast the subject of class type that w is as an Subject Object. It will work as expected if I just say new MyClass instead of the activator. I really cant find any solutions online either, so does anyone know what is going on here?
I'm a bit tired here so I'm having a problem groking what you want to achieve here but one thing that stands out is that you are calling a generic Publish method with a reflected object and expecting Subject to know what type the subject is.
This space for rent
-
Im trying to publish a message that should communicate between classes:
public sealed class EventMessager : IEventMessager { private readonly Dictionary subscriberLookup = new Dictionary(); public IObservable Observe() { if (!subscriberLookup.TryGetValue(typeof(T), out object subject)) { subject = new Subject(); subscriberLookup.Add(typeof(T), subject); } return ((ISubject)subject).AsObservable(); } public void Publish(T @event) { if (subscriberLookup.TryGetValue(@event.GetType(), out object subject)) { ((Subject)subject).OnNext(@event); } } }
This works well, except for the time I wanted to use Reflection in order to initate a new Message class like so:
eventMessager.Publish(Activator.CreateInstance(w[0]));
It complains that it cannot cast the subject of class type that w is as an Subject Object. It will work as expected if I just say new MyClass instead of the activator. I really cant find any solutions online either, so does anyone know what is going on here?
Activator.CreateInstance(Type)
returns anObject
. Activator.CreateInstance Method (System) | Microsoft Docs[^] As a result, you are callingeventMessager.Publish<object>(instanceOfYourType)
. That uses the run-time type of the parameter to find the subscriber. This will be aSubject<YourClass>
instance. You then try to cast that instance toSubject<object>
, which fails because theSubject<T>
class is not covariant on its type parameter. Covariance and Contravariance in Generics | Microsoft Docs[^] You can't even get away with casting the subscriber to theIObserver<T>
interface, since that interface is contravariant. You're going to need to use reflection to invoke the method. I'd suggest using a separate method to avoid making the regular method slower:public void PublishNew(Type eventType)
{
if (subscriberLookup.TryGetValue(eventType, out object subject))
{
object @event = Activator.CreateInstance(eventType);
Type t = typeof(IObserver<>).MakeGenericType(eventType);
MethodInfo onNext = t.GetMethod("OnNext");
onNext.Invoke(subject, new[] { @event });
}
}...
eventMessager.PublishNew(w[0]);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Activator.CreateInstance(Type)
returns anObject
. Activator.CreateInstance Method (System) | Microsoft Docs[^] As a result, you are callingeventMessager.Publish<object>(instanceOfYourType)
. That uses the run-time type of the parameter to find the subscriber. This will be aSubject<YourClass>
instance. You then try to cast that instance toSubject<object>
, which fails because theSubject<T>
class is not covariant on its type parameter. Covariance and Contravariance in Generics | Microsoft Docs[^] You can't even get away with casting the subscriber to theIObserver<T>
interface, since that interface is contravariant. You're going to need to use reflection to invoke the method. I'd suggest using a separate method to avoid making the regular method slower:public void PublishNew(Type eventType)
{
if (subscriberLookup.TryGetValue(eventType, out object subject))
{
object @event = Activator.CreateInstance(eventType);
Type t = typeof(IObserver<>).MakeGenericType(eventType);
MethodInfo onNext = t.GetMethod("OnNext");
onNext.Invoke(subject, new[] { @event });
}
}...
eventMessager.PublishNew(w[0]);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Ah, that works perfectly, thank you.
-
Activator.CreateInstance(Type)
returns anObject
. Activator.CreateInstance Method (System) | Microsoft Docs[^] As a result, you are callingeventMessager.Publish<object>(instanceOfYourType)
. That uses the run-time type of the parameter to find the subscriber. This will be aSubject<YourClass>
instance. You then try to cast that instance toSubject<object>
, which fails because theSubject<T>
class is not covariant on its type parameter. Covariance and Contravariance in Generics | Microsoft Docs[^] You can't even get away with casting the subscriber to theIObserver<T>
interface, since that interface is contravariant. You're going to need to use reflection to invoke the method. I'd suggest using a separate method to avoid making the regular method slower:public void PublishNew(Type eventType)
{
if (subscriberLookup.TryGetValue(eventType, out object subject))
{
object @event = Activator.CreateInstance(eventType);
Type t = typeof(IObserver<>).MakeGenericType(eventType);
MethodInfo onNext = t.GetMethod("OnNext");
onNext.Invoke(subject, new[] { @event });
}
}...
eventMessager.PublishNew(w[0]);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Richard Deeming wrote:
I'd suggest using a separate method to avoid making the regular method slower:
I found some other lower level code that the author claimed was 10 times faster than Activator.CreateInastance:
public class ObjectCreateMethod
{
delegate object MethodInvoker();
MethodInvoker methodHandler = null;public ObjectCreateMethod(Type type) { CreateMethod(type.GetConstructor(Type.EmptyTypes)); } public ObjectCreateMethod(ConstructorInfo target) { CreateMethod(target); } void CreateMethod(ConstructorInfo target) { DynamicMethod dynamic = new DynamicMethod(string.Empty, typeof(object), new Type\[0\], target.DeclaringType); ILGenerator il = dynamic.GetILGenerator(); il.DeclareLocal(target.DeclaringType); il.Emit(OpCodes.Newobj, target); il.Emit(OpCodes.Stloc\_0); il.Emit(OpCodes.Ldloc\_0); il.Emit(OpCodes.Ret); methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker)); } public object CreateInstance() { return methodHandler(); } }
But you have solved my problem, so this does not bother me as much.