0

Looking for some help with a blocker I'm having on my application.

For some context, this is a follow up to another question I've done: C# Validate that generic type extends generic abstract class with type parameters

Regarding the issue, on my project, the overall structure is the following:

    public abstract class AbstractPlugin<TSignatureFile, TLoadedFile> : 
        IPlugin<TSignatureFile, TLoadedFile> 
        where TSignatureFile : IFileSignature
        where TLoadedFile : LoadedFile { }

    public interface IPlugin<out TSignatureFile, TLoadedFile> 
        where TSignatureFile : IFileSignature
        where TLoadedFile : LoadedFile { }

    public class MyPlugin : AbstractPlugin<MyPluginFileSignature, MyPluginFile { }

    public class MyPluginFileSignature : IFileSignature { }

    public class MyPluginFile : LoadedFile { }

This "MyPlugin is loaded from an external DLL where I create an Instance using:

var result = Activator.CreateInstance(type, notificationCallBack);

"notificationCallBack" is a constructor parameter.

The issue I'm having is that even though checking everything in terms of the type, what implements, etc it looks good, if I try to cast this "result" object to "IPlugin<IFileSignature, LoadedFile>" it fails the cast.

From my prespective this should work, as the instanciated object respects and implements this interface.

Could you please shed me some light on the subject, this is in a way blocking me from proceding further :(

Thanks

Ralms
  • 463
  • 5
  • 13
  • 4
    The interface `IPlugin` is invariant with respect to the `TLoadedFile` parameter. You can't take something which implements `IPlugin` and cast it to `IPlugin` unless the `TLoadedFile` parameter is covariant. You've marked `TFileSignature` as covariant, so you must be aware of variance – canton7 Aug 03 '20 at 12:36
  • yeah the subject of covariance and contravariance has been something that I'm struggling to understand. I've read a bit about it after having a similiar issue and that is where marking TFileSignature as covariant came from. So as "MyPluginFile" extends LoadedFile, should I just mark it as covariant also in the interface? Thanks – Ralms Aug 03 '20 at 12:40
  • 1
    If you can, then yes, marking `TLoadedFile` as covariant will solve your issue. The problems arise if `IPlugin` has a method/property which accepts a parameter of type `TLoadedFile`: in this case, your `MyPlugin` type would have a method which accepts a `MyPluginFile`; however, if you were able to cast `MyPlugin` to an `IPlugin` then you would be able to call that method and pass in a `LoadedFile`. This would break, since your method expects a `MyPlugin` (or one of its children), and not a `LoadedFile` (or one of its parents). – canton7 Aug 03 '20 at 12:41
  • If you mark `TLoadedFile` as `out`, then the compiler will ensure that you do not have any methods/properties which accept a `TLoadedFile`. This lets it relax that restriction and lets you cast an `IPlugin` to a `Plugin`: the compiler has ensured that you're not able to pass in any parameters of `TLoadedFile`, so there's no possibility of breakage – canton7 Aug 03 '20 at 12:45
  • Do you guys have some recomendations on good reads regarding variance in .Net? I want to see if I understand it properly as making TLoadedFile Covariant is not prooving to be very straight forward. – Ralms Aug 03 '20 at 12:54
  • 1
    [This doc](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) is decent. C# In Depth also has a good explanation (and you should already have a copy of that on your bookeshelf, right? ;) – canton7 Aug 03 '20 at 12:55
  • 1
    No but I probably should :D Thanks, checking it out. – Ralms Aug 03 '20 at 13:06

1 Answers1

1

Leaving what I've changed to make this work.

Thanks to @canton7 for the help.

So this was in fact a variance issue.

I've decided to move away from forcing the "LoadedFile" abstract class and instead changed everything to use interfaces, in this case "ILoadedFile".

Also, changed IPlugin to have the generic parameter TLoadedFile Covariant. Here is more details on what I've changed which might help someone.

To make TLoadedFile covariant, I had to also change another interface that was using that generic parameter, in this case was IFileService.

    public interface IPlugin<out TSignatureFile, out TLoadedFile> 
        where TSignatureFile : IFileSignature
        where TLoadedFile : ILoadedFile
    {
        ...
        public IFileService<TLoadedFile> FileService { get; }
        ...
    }

    public interface IFileService<out TLoadedFile> where TLoadedFile : ILoadedFile
    {
        public TLoadedFile NewFile(string filepath, string safeFileName);
    }
Ralms
  • 463
  • 5
  • 13