C
C#16mo ago
Silva

✅ Receiving any instance of a generic class in a method, no matter what the generic type is.

Title, how can I allow for a method to receive any instance of a generic class, no matter what the generic type is. So a method that could receive both Generic<int> and Generic<string>. Doing public void MyMethod(Generic param) doesn't work
101 Replies
Silva
SilvaOP16mo ago
An option would be to have a non-generic superclass, but is there another way that doesn't force me to inherit?
Pobiega
Pobiega16mo ago
uhm, can the method itself be generic?
Angius
Angius16mo ago
^
Silva
SilvaOP16mo ago
That would solve it, but no
Pobiega
Pobiega16mo ago
then ya fucked
Silva
SilvaOP16mo ago
Hahah So I'm stuck with creating a superclass just so I can reference the object?
Pobiega
Pobiega16mo ago
¯\_(ツ)_/¯ MyMethod<T>(MyGeneric<T> x) is the "proper" solution I'm afraid
Angius
Angius16mo ago
IList maybe...? You lose the generic, though
Silva
SilvaOP16mo ago
Maybe I can be more specific: I have two lists of GenericA and GenericB. Both inherit from NonGeneric. I have a façade that creates GenericA and GenericB, and stores those in an internal list to the façade, so that later I can call a method that deletes those Generics. When creating the Generics, obviously I need to pass in the type. When cleaning up, all I need is a way to remove it from a list I could accept types of "NonGeneric", but then My cleanup method for types of GenericA is different from types of GenericB, so I'd have to do some type checking to guarantee that the user passed a GenericA into CleanupGenericA and not a GenericB Does this make sense?
Denis
Denis16mo ago
And this?
void MyMethod(Object obj, Type t)
{
var convertedObject = Convert.ChangeType(obj, t);
...
}
void MyMethod(Object obj, Type t)
{
var convertedObject = Convert.ChangeType(obj, t);
...
}
Source: https://stackoverflow.com/a/12234140
Stack Overflow
How to cast Object to its actual type?
If I have: void MyMethod(Object obj) { ... } How can I cast obj to what its actual type is?
Silva
SilvaOP16mo ago
That would be the same as having the method generic, no?
Angius
Angius16mo ago
I'd rather use pattern matching
Denis
Denis16mo ago
Pattern matching is better... Yeah
Angius
Angius16mo ago
string Foo(IList l)
{
return l switch {
List<int> => l.Sum().ToString(),
List<string> => string.Join(", ", l),
_ => "unsupported type"
};
}
string Foo(IList l)
{
return l switch {
List<int> => l.Sum().ToString(),
List<string> => string.Join(", ", l),
_ => "unsupported type"
};
}
I wonder if that works, considering covariance, contravariance, all those shenanigans
Silva
SilvaOP16mo ago
I'm confused about this IList
Angius
Angius16mo ago
Why?
Denis
Denis16mo ago
It's just an interface inherited by List
Unknown User
Unknown User16mo ago
Message Not Public
Sign In & Join Server To View
Silva
SilvaOP16mo ago
Right, in my case I'm not receiving lists
Unknown User
Unknown User16mo ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX16mo ago
Angius
REPL Result: Success
IList a = new List<int>(){1, 2, 3};
IList b = new List<string>(){"a", "b", "c"};

new {
a,
Ais = a is List<int>,
b,
Bis = b is List<string>
}
IList a = new List<int>(){1, 2, 3};
IList b = new List<string>(){"a", "b", "c"};

new {
a,
Ais = a is List<int>,
b,
Bis = b is List<string>
}
Result: <>f__AnonymousType0#1<IList, bool, IList, bool>
{
"a": [
1,
2,
3
],
"ais": true,
"b": [
"a",
"b",
"c"
],
"bis": true
}
{
"a": [
1,
2,
3
],
"ais": true,
"b": [
"a",
"b",
"c"
],
"bis": true
}
Compile: 483.590ms | Execution: 77.641ms | React with ❌ to remove this embed.
Denis
Denis16mo ago
What are you receiving and what is your end goal?
Angius
Angius16mo ago
Ah, yeah, a generic class You could use a marker interface the very same way Just an empty interface IStuff {}
Silva
SilvaOP16mo ago
Right
Angius
Angius16mo ago
And then pattern match it as needed
Denis
Denis16mo ago
Maybe since the language isn't allowing a simple solution for this, the desired goal is incorrect?
Silva
SilvaOP16mo ago
Perhaps. I might be sticking too close to my python background which is a bit more flexible in this regard
Denis
Denis16mo ago
And is the signature of your.method constrained? Can you add extra parameters?
Silva
SilvaOP16mo ago
I can add extra parameters, yes I can give a concrete example
Denis
Denis16mo ago
Crazy idea
Angius
Angius16mo ago
Well, again, it would be best to make the method itself generic
Denis
Denis16mo ago
Maybe you could add a parameter for a method delegate that would do what you need with type. And why again can't you use generics? I mean a generic method
Silva
SilvaOP16mo ago
I can
Angius
Angius16mo ago
Well Problem solved then
Denis
Denis16mo ago
But you said this
Silva
SilvaOP16mo ago
I was overly enthusiastic
Denis
Denis16mo ago
🤔
Silva
SilvaOP16mo ago
Nothing is effectively stopping me But I would like to avoid it
Angius
Angius16mo ago
why
Silva
SilvaOP16mo ago
I just have a construction method and a deconstruction method. Didn't want users of the API to have to repeat the type of the constructed object in the deconstruction
Denis
Denis16mo ago
Oh, well then don't create these challenges for yourself
Angius
Angius16mo ago
"Nothing stops me from using a spoon to eat this soup, but I would like to avoid it"
Denis
Denis16mo ago
I use a knife to eat soup, was I doing it wrong my whole life...
Angius
Angius16mo ago
Well you can just make the class itself generic, then Then it will propagate to the methods, the ctor, the dtor, everything
Silva
SilvaOP16mo ago
It's a static class
Angius
Angius16mo ago
oof Wait Why does a static class have a constructor And a deconstructor ???
Denis
Denis16mo ago
Static constructor is ... fine
Silva
SilvaOP16mo ago
Sorry, shouldn't have used those words
Angius
Angius16mo ago
Not even "why", "how"
Denis
Denis16mo ago
But a deconstructor?
Silva
SilvaOP16mo ago
The class has methods to create and destroy instances of my generic classes
Angius
Angius16mo ago
Wait
Denis
Denis16mo ago
Maybe share the class? This would clear some confusion
Angius
Angius16mo ago
Some sort of
class Foo {}

static class Bar {
public Foo GetInstance() => new Foo();
}
class Foo {}

static class Bar {
public Foo GetInstance() => new Foo();
}
? I'm getting more and more confused by the second lol
Denis
Denis16mo ago
Confusion go brrr
Silva
SilvaOP16mo ago
Sorry! I'll create a concrete example of what I'm doing and share it here, since I can't share the source code
Denis
Denis16mo ago
$code
MODiX
MODiX16mo ago
To post C# code type the following: ```cs // code here ``` Get an example by typing $codegif in chat If your code is too long, post it to: https://paste.mod.gg/
Denis
Denis16mo ago
Just in case
Silva
SilvaOP16mo ago
I'm aware, but thanks, appreciate it :) Okay, I think this might be a good example
public static class Facade
{
private static readonly Dictionary<string, List<Publisher>> TopicPublishers = new();
private static readonly Dictionary<string, List<Subscriber>> TopicSubscribers = new();

public static Publisher<T> CreatePublisher<T>(string topic)
{
// some creation logic
var pub = new Publisher<T>(topic);
TopicPublishers[topic] = pub;
}

public static void RemovePublisher(Publisher pub)
{
// some deletion logic
TopicPublishers[pub.Topic].Remove(pub);
}
}
public static class Facade
{
private static readonly Dictionary<string, List<Publisher>> TopicPublishers = new();
private static readonly Dictionary<string, List<Subscriber>> TopicSubscribers = new();

public static Publisher<T> CreatePublisher<T>(string topic)
{
// some creation logic
var pub = new Publisher<T>(topic);
TopicPublishers[topic] = pub;
}

public static void RemovePublisher(Publisher pub)
{
// some deletion logic
TopicPublishers[pub.Topic].Remove(pub);
}
}
Example usage:
var publisher = Facade.CreatePublisher<int>("topic");
publisher.DoThings();

//Ideally, to make the API simpler, avoid having to do RemovePublisher<int>:
Facade.RemovePublisher(publisher);
var publisher = Facade.CreatePublisher<int>("topic");
publisher.DoThings();

//Ideally, to make the API simpler, avoid having to do RemovePublisher<int>:
Facade.RemovePublisher(publisher);
Angius
Angius16mo ago
So it's a weitd singleton kinda thing? A substitute for having straight out global variables?
Silva
SilvaOP16mo ago
Yeah, its an Event Aggregator But I have a layer in front to create all my Publishers and Subscribers So the user doesn't have to know about all the intricate details and instead can just worry about using the entities it creates
Angius
Angius16mo ago
Well, my first instinct is "static bad, don't do that, use DI or something instead"
Denis
Denis16mo ago
In the example usage you create a publisher. Imagining that you do not use generics... How would the method or the user know what generic type is created? And yeah... Maybe DI or mediator is something you should consider using
Silva
SilvaOP16mo ago
Well I currently have this working by passing the generic type in as a parameter instead So:
public static Publisher CreatePublisher(string topic, Type eventType)
public static Publisher CreatePublisher(string topic, Type eventType)
Denis
Denis16mo ago
The user of a C# library would prefer generics in this case
Silva
SilvaOP16mo ago
I suppose I could do this:
public static Publisher CreatePublisher<T>(string topic) {
var type = typeof(T)
...
}
public static Publisher CreatePublisher<T>(string topic) {
var type = typeof(T)
...
}
and the rest of my code looks the same, and the user would still have the generics
Denis
Denis16mo ago
Yes, not sure why you need to get the typeof Because you create generics simply by new Generic<T>()
Silva
SilvaOP16mo ago
I mostly do it to validate that no other publisher is created for a given topic with a different type So I can't create a Publisher for topicA with type int, and a Publisher for topicA with type string
Denis
Denis16mo ago
Ooook, thinking Yes typeof should be fine
Silva
SilvaOP16mo ago
Cool, I'll give that a go and report back Could you give me a bit more information as to why this is your first instinct? I think I know why but just to get your point of view Actually, I take back what I said. At one point I loop through the TopicSubscribers and call subscriber.ReceiveMessage(message), where message is of the type that was given. Problem is that since it's not a generic, message has to be an object to allow for anything to be sent. If it's an object, when the client receives the message, they will have to cast it to their type, which is even more of a problem I feel like that message was confusing
Angius
Angius16mo ago
It gives unrestricted global access Breaks encapsulation and all that good stuff
Silva
SilvaOP16mo ago
Can you explain how DI doesn't do this?
Angius
Angius16mo ago
I need to explicitly inject a given singleton where I need it Only then does it become available If I don't inject it, I have no access to it
Silva
SilvaOP16mo ago
This might be my lack of experience talking, but you do have access to the DI framework itself, isn't that in a way something you can access everywhere?
Angius
Angius16mo ago
I have access to serives registered in the DI container only in other services also registered in that container So no, not global either Everything's on an opt-in basis
Silva
SilvaOP16mo ago
But something somewhere has to be global, no? You need to somehow be able to say "hey, I want this reference", and however you do that, must be something that is global Maybe its an attribute that marks something so that the DI framework knows what to inject
Angius
Angius16mo ago
No, nothing needs to be global If I make a random
public class Foo
{

}
public class Foo
{

}
class, it will not have access to the DI unless I explicitly registed it with, say
services.AddSingleton<Foo>();
services.AddSingleton<Foo>();
Silva
SilvaOP16mo ago
Again, my lack of experience is showing. Could you explain what you mean by "have access to the DI"? I thought DI was something along the lines of
public class Foo
{
[Inject]
public Bar bar;

//Then, inside my methods, I know I have a Bar even though I did not construct it or get a reference to it
}
public class Foo
{
[Inject]
public Bar bar;

//Then, inside my methods, I know I have a Bar even though I did not construct it or get a reference to it
}
Angius
Angius16mo ago
Yes, sure, you can do that But only if Foo itself is registered in the DI container
Silva
SilvaOP16mo ago
So you have a class somewhere that handles all these registrations?
Angius
Angius16mo ago
The main method, for example
Silva
SilvaOP16mo ago
Sure, but then at that level you need to have everything available. So if you have 100 entities you need to call a registration method 1 time for each entity (100 times total)
Denis
Denis16mo ago
If it is a generic one, you can register it once, I believe But yes, for each entity type, you have to register it
Angius
Angius16mo ago
What matters is you need to be explicit about it
Denis
Denis16mo ago
E.g., you register a logger that is used thoughout the app
Silva
SilvaOP16mo ago
Right, you set the logger to some type of app-level scope and the DI framework creates one logger and everyone uses the same one
Denis
Denis16mo ago
The same type
Silva
SilvaOP16mo ago
Same type or same instance?
Denis
Denis16mo ago
Not the same instance, unless you configure it
Silva
SilvaOP16mo ago
If it is scoped that way
Denis
Denis16mo ago
Samenisntance if you configure it as singleton Yes
Silva
SilvaOP16mo ago
Right I do still have a hard time making the connection that "it's intuitively better to just jump from the system I'm implementing to a DI framework" When what I have is effectively a number of events that I can invoke at any time, and a number of observers interested in those events But a central place to register those observers on the events With an abstraction over the top to simplify the api If I was using DI I'd have to have one point where I'd register all my events, and then both invokers and observers would have to request a reference to that event. In my system, it's all abstracted behind one class.
Angius
Angius16mo ago
Sure, but I can, randomly, from anywhere in the code, call Facade.RemovePublisher() I could do that in the finalizer of a DTO Or in a property getter in a record Or in a Razor template It's unrestricted
Silva
SilvaOP16mo ago
Sure But if we are coding on the principle that the user is dangerous, then we need to safeguard against everything
Angius
Angius16mo ago
Yes
Silva
SilvaOP16mo ago
Hahah I guess I should have seen that coming Although, I could argue that the user can still configure the DI framework so they can call RemovePublisher from a DTO or from anywhere really Sure it's more contrived, they need to add more lines of code to be able to do something that isn't ideal, but they can still do that
Angius
Angius16mo ago
Yes, but they are being explicit about wanting to do that Injecting a IPublisherService into a class is an explicit statement of intent that this class will do stuff with publishers
Silva
SilvaOP16mo ago
Well, to be fair, I do think that my system is quite explicit:
this.pub = Facade.Publisher(...);

// Later
this.pub.Publish(...);
this.pub = Facade.Publisher(...);

// Later
this.pub.Publish(...);
If they create and store the publisher, then they are going to do something with publishers But I think I do understand what you are saying https://martinfowler.com/articles/injection.html This I think represents a little bit what we are discussing I think it's more of a matter of where you want to be explicit, not as much as if it is or isn't explicit? "The first point is that both implementations provide the fundamental decoupling that's missing in the naive example - in both cases application code is independent of the concrete implementation of the service interface. The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control." (highlight is my own) Anyway, sorry if I derailed the conversation, but it did genuinely help me understand some more concepts! Thanks @ZZZZZZZZZZZZZZZZZZZZZZZZZ and @Denis for the help, and thanks for the genuinely interesting conversation! :)
Angius
Angius16mo ago
Anytime Ok
Accord
Accord16mo ago
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.
Want results from more Discord servers?
Add your server