❔ Generic class to fire on data change according to data type
Hi everyone. I am tring to implement generic class that holds generic type of data class. And I will store them in a dictionary and fire their function according to type of data class. Please check code for more detail.
Here is my interfaces
Classes that inherits them
Then I want to add them to a dictionary and call according to their data type.
Eventhough MyData and MyData2 inherits IMyDataInterface, it gives me not assignable to parameter type error.
And here is the purpose of that implementation. It should trigger related listener's OnDataChange function
Can you please guide me about how should I implement that, or what is wrong with the code?
38 Replies
Covariance and Contravariance (C#)
Learn about covariance and contravariance and how they affect assignment compatibility. See a code example that demonstrates the differences between them.
Take say the classes A and B where
B : A
. You can have an instance of B stored as a reference of A, but say you add a layer by making it a list, that can't work anymore. A List<A>
may contain As or B, but a List<B>
can only contain Bs. So if you were able to do List<A> list = new List<B>()
, you could then do list.Add(new A())
which can't work if the runtime type is List<B>
so you can't assume List<B>
is compatible with List<A>
the same way A and B are.
A work around is to add the in
and out
keywords to the generics, limiting what you can do with the generics to restore that compatibility.
IEnumerable<T> is an example of that. Since T is only used as output and has the out keyword, you can do IEnumerable<A> = new List<B>()
First of all, thank you very much for your explanation. But there are a couple of parts that don't quite get. First of all, in the case you gave,
A
is actually a B
, since class A
inherits B
, so we can use it instead of B ( simple inheritance ). I actually think I did the same thing here. Since Listeners inherit from IMyDataListener
, I need to be able to use both.That was a typo, I meant B : A
Alright, so it is something like that. And yes It is basic inheritance
But what you're trying to do with
new MyDataListener()
in the dictionary is equivalent to List<A> = new List<B>()
So It also works when I switch from
to
Am I?
MyDataListener : IMyDataListener<MyData>
The holder is interface and I am tring to cast class to that interface
MyData would be B and IMyDataInterface would be A
Yes I was coming to that
Listening?
Seeing your messages yes
Sorry i mean I was listening 🙂 (lack of my english)
Just not sure what you mean by this
I mean, MyData can be convertable to IMyDataInterface without any error
Yes, but it's back to the list example
The compiler can't make assumptions on how which way the generic is assignable with its base type
So it assumes it's not assignable
Yeah I guess the problem is about unboxing it right?
As mentioned in the documentation It does work If I use out keyword
You would have to define the dictionary using the non generic IMyDataListener
or use out
By adding that keyword, you ensure the generic can only be used as output. That way the compiler can assume the assigning will work
The rule of thumb is the runtime type must never be more strict than the declaring type
Without generics, that boils down to simply being able to do
A a = new B()
since all you can do with A, B can also do
There's also only one instance at play so you can't mess things up like putting an A where a B is expected
When it's through generic, the "no stricter than declaring" can only be true if the generic is only used as outputAlright, thank you sir. I would love to check that deeply, I did not know that deeply.
So what do you think should I do since I am not able to use in keyword I will not be able to get data in parameter. When I use out I am only available to use it as output.
Or do you have any suggestion for me to implement that kind of listener thing in a better way?
Declare the dictionary with IMyDataListener instead of the generic one
I tried that, but things gets messy when I try to call functions
True, you can't really have a non generic IMyDataListener since it takes it as a parameter
You could if just to have references to them that you can later cast to the generic one
What does the listener do anyway?
Here is my real code
It is a generic listener class that listens given message from given client
And the type of the message isn't known at compile time
and here is the example
You could have two methods: One generic that takes a specific message type so you can cast the listener using that generic
Yes it depends on the message, But overall I know all of them
And a non generic method that calls the generic one using the right type based on the runtime type
Or just cast the listener to the right generic one right away based on the message type using a single method
if (message is OKMessage ok) ((OKListener) listener).OnMessageReceived(ok)
I actually am loading them automatically from assembly then register them to the dic
I will try the first one
Then you can still do it with refelction
Keep the non generic listener interface in the dictionary definition but make the OnMessageReceived call through reflection
I'd say add references to the MethodInfos of listener.OnMessageReceived for each one so you don't have to get it every time
Thank you, I also think that but since it is server-client message using reflection in every tiny message in a mobile game will probably cause performance issue. I will try some caching for that 👍
What do you mean adding referans to MethodInfo?
Depending on how the listener types are created, you might need to creates references to them through reflection using CreateGenericType<>()
So TValue in the dictionary would be a class or struct containing the instance and MethodInfo obtained from the generic listener Type
Alright thank you so much. It was an informative talk for me. Let me check them all and try my best 🙂
Or for a lesser performance impact, give the listeners an overlapd that takes any message and validates the type ay runtime
You could even pass on that overload to a base non generic listener interface and completely avoid casting and reflection, only casting in OnMessageReceived
Yes, it looks like a better solution 👍
I will implement that too 👍
To avoid burdening each listener class with validating the message type, you could replace the generic interface with a generic abstract class that implements the base message method and does tbe validation. The listeners then only need implement the method with the respective message type.
Having the dictionary be <Type, IDataListener>, get the listener and pass the message as is
Alright, thank you so much for taking the time to help
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.