3 ms·
Hi, sorry for the delay, had to pop into a meeting. I tried to make a very scaled down example of what I mean. It's hard to imagine a real-world scenario from t
by mhomde 9y ago
Hi, sorry for the delay, had to pop into a meeting. I tried to make a very scaled down example of what I mean. It's hard to imagine a real-world scenario from this but assume that according to the architecture we need to separate concerns this way.
The point is that in some cases you want to be able to pass a more general interface while getting some Type-checking and convenience of generics.
In this case we want to hold on to PopProtocolSender and perhaps use it as a variable somewhere. When we use it we need to acccess it's specific IPopProtocol Protocol Property. But we also want to be able to treat it as a general IProtocolSender that has a IProtocol Property, so it can be sent to other classes that takes that interface.
These kinds of situations mainly arise when you have a more complicated architecture. If there's a better way to have your cake and eat it too I'd be glad to know it :)
public interface IPopProtocol : IProtocol
{
void SomeUniqueMetod();
}
public interface IProtocol
{
}
public interface IProtocolManager
{
void SendMessage(IProtocol protocol, string message);
}
public interface IProtocolSender
{
IProtocol Protocol
{
get;
}
}
public class PopProtocol : IPopProtocol
{
public void SomeUniqueMetod()
{
}
}
public class Program
{
public void DoStuff()
{
var sender = new PopProtocolSender ();
var manager = new ProtocolManager();
manager.SendMessage(sender.Protocol, "Testing");
sender.Protocol.SomeUniqueMethod();
}
}
public class ProtocolManager : IProtocolManager
{
public void SendMessage(IProtocol protocol, string message)
{
}
}
public class ProtocolSender : IProtocolSender
{
protected readonly IProtocol _protocol;
public ProtocolSender(IProtocol protocol)
{
_protocol = protocol;
}
public IProtocol Protool => _protocol;
}
public class PopProtocolSender : ProtocolSender<IPopProtocol>
{
public PopProtocolSender() : base(new PopProtocol)
{
}
}
public class ProtocolSender<T> : ProtocolSender where T : IProtocol
{
public ProtocolSender(T protocol) : base(protocol)
{
}
public new T Protocol => (T)_protocol;
}
- GenericsMotors 9y agoI understand what you're getting at, but why not simply make both IProtocolSender and ProtocolSender generic. Here's a functioning example I based on yours that just compiled and ran in LinqPad without any issues: public interface IPopProtocol : IProtocol { void SomeUniqueMethod(); } public interface IProtocol { } public interface IProtocolManager { void SendMessage(IProtocol protocol, string message); } public interface IProtocolSender<out T> where T : IProtocol { T Protocol { get; } } public class PopProtocol : IPopProtocol { public void SomeUniqueMethod() { Console.WriteLine($"Hi, I'm {nameof(PopProtocol)}"); } } void Main() { var sender = new ProtocolSender<IPopProtocol>(new PopProtocol()); var manager = new ProtocolManager(); manager.SendMessage(sender.Protocol, "Testing"); sender.Protocol.SomeUniqueMethod(); } public class ProtocolManager : IProtocolManager { public void SendMessage(IProtocol protocol, string message) { } } public class ProtocolSender<T> : IProtocolSender<T> where T : IProtocol { private T _protocol; public T Protocol => _protocol; public ProtocolSender(T protocol) { _protocol = protocol; } } Not sure what the fuss is: you can use SendMessage as usual, and also have access to SomeUniqueMethod. EDIT: Also please note that the signature for the generic IProtocolSender uses the out keyword, making it covariant. This will allow you to cast IProtocolSender<IPopProtocol> to IProtocolSender<IProtocol> if you need to, or to pass an IProtocolSender<IPopProtocol> object to a method that expects an IProtocolSender<IProtocol>.
- mhomde 9y agobecause then a generic requirement will propagate through the whole framework. And some polymorphism/patterns simply won't be possible. Trust me, I've encountered this problem several times
- louthy 9y agoI've been writing c# for around 12 years (on multi million line code bases) and have never had the problems you state, and the example you gave has been shown to not be an issue. The idea that the propagation of type safety is an issue is something I find bewildering, sorry.
- mhomde 9y agoWell, I've been writing C# for 17 years, and I've came across it twice, so it is a very niche solution for certain circumstances. I can't say it's the only solution, but in those instances it was the one that offered the best type safety and allowed classes to have non-generic interfaces. Say that we have some kind of datastorage where everything is an IEntity, and we need to be able to treat them as such. Them we have a specific entity IApple : IEntity. Every IEntity has IVersion as a Version property, that is passed in it's implementation's construction. IApple has a more specific IAppleVersion as it's Version Property. When we're working with an IApple instance we want to be able to have access to the version as IAppleVersion. But other classes handles only IEntities with IVersions. In this case it makes sense to have the Version as a generic parameter to the class: Apple : Entity<IAppleVersion> which makes sure its constructor is: Apple(IAppleVersion version). We need to put the stuff that's shared between all entities in the non-generic Entity class, add the Typing of version in a generic version of Entity so Apple can expose the type provided. The Apple class that implements IApple has specific methods for its type. Both entity classes are abstract so they will never be initiated. I'm not sure that's clear but I hope you can see the problem that I'm trying to solve. I think it's the disconnect with wanting to be able to access both more specific and more general interfaces (and their properties) that causes trouble. On the flip side, if we have a legitimate need for both a more specific and a more general version of an class/interface, can you tell me a specific reason the way I showed would be bad. It's easy to get hung up "new" is bad because it's 95% true, but as with everything there are exceptions. But if you never have the need for it, don't use it. I stand by my opinion though that you should be careful and in some situations be able to provide non-generic interfaces rather than requiring each method and class in a library to be generic, but that's only my opinion man :)