1

I have the following interface which contains a static method definition:

public interface IUpgradableDataSource { static abstract Task<JsonElement> UpgradeSourceConfigurationAsync( JsonElement configuration, CancellationToken cancellationToken ); } 

This interface is implemented by dynamically loaded plugins. To invoke UpgradeSourceConfigurationAsync on them, I need to use reflection which I would like to avoid (dataSourceType is the plugin's type):

var dataSourceType = <the System.Type of the plugin> var dataSourceInterfaceTypes = dataSourceType.GetInterfaces(); if (dataSourceInterfaceTypes.Any(x => x == typeof(IUpgradableDataSource))) { var methodInfo = dataSourceType .GetMethod( nameof(IUpgradableDataSource.UpgradeSourceConfigurationAsync), BindingFlags.Public | BindingFlags.Static )!; var upgradedConfiguration = await (Task<JsonElement>)methodInfo.Invoke( default, [ configuration, cancellationToken ] )!; // ... } else { // ... } 

I am implementing exactly the same code also in Python where I can solve this problem quite elegant:

if issubclass(data_source_type, IUpgradableDataSource): upgraded_configuration = await data_source_type.upgrade_source_configuration(configuration) ... else ... 

By simply using issubclass I get proper intellisense support and pyright is also happy:

enter image description here

My question is: Is there something similar in C#? I.e. can I just check if the System.Type instance is deriving from IUpgradableDataSource and then invoke static methods defined on that type?

I tried the following but it does not work (compile-time error):

var myCastedType = dataSourceType as IUpgradableDataSource; await myCastedType.UpgradeSourceConfigurationAsync(...) 
2
  • 1
    You need to get an instance and the just cast/use as to work with it. You should work with concrete instance, not an instance of the System.Type. Without seeing full code it is hard to tell what you need to do, but in some cases you can use Activator.CreateInstance: var castedInstance = Activator.CreateInstance(dataSourceType) as IUpgradableDataSource; if(castedInstance is not null) ... Commented Feb 2 at 21:22
  • I found it cleaner first to work with the C# 11 static abstract interface methods but it causes much trouble so I will switch back to an ordinary instance method. Commented Feb 4 at 9:29

1 Answer 1

3

You could have achieved this much more easily if you were working with instance methods:

if (dataSourceInstance is IUpgradableDataSource myCastedInstance){ await myCastedInstance.UpgradeSourceConfigurationAsync(...) } 

Static interface methods are meant to be called mostly in constrained methods like T.Method()(see below).

As an aside, your implementation really excludes the possibility of implementing the interface method in the plugin with Explicit Interface Implementation:

public class Plugin : IUpgradableDataSource { static Task<JsonElement> IUpgradableDataSource.UpgradeSourceConfigurationAsync(JsonElement configuration, CancellationToken cancellationToken) { throw new NotImplementedException(); } } 

The method above would not be discovered with your reflection logic.

To work around this issue and make matters simpler, it would be very useful to define a helper method with the constraint T : IUpgradableDataSource somewhere:

public static Task<JsonElement> UpgradeSourceConfigurationAsyncHelper<T>( JsonElement configuration, CancellationToken cancellationToken ) where T : IUpgradableDataSource => T.UpgradeSourceConfigurationAsync(configuration, cancellationToken); 

We would use this method as a template to construct delegates so we can invoke each plugin's implementation from reflection:

var dataSourceType = pluginType; // TODO: make static field var helperGenericMethod = typeof(ClassWithHelperMethod).GetMethod("UpgradeSourceConfigurationAsyncHelper"); if (dataSourceType.GetInterfaces().Any(x => x == typeof(IUpgradableDataSource)) /* alternatively dataSourceType.IsAssignableTo(typeof(IUpgradableDataSource))*/) { // TODO: // possibly cache in Dictionary<Type, Func<JsonElement, CancellationToken, Task<JsonElement>>> var del = helperGenericMethod .MakeGenericMethod(dataSourceType) .CreateDelegate<Func<JsonElement, CancellationToken, Task<JsonElement>>>(); var upgradedConfiguration = await del(configuration,cancellationToken); } 
Sign up to request clarification or add additional context in comments.

3 Comments

The var helperGenericMethod = should probably move inside the if. And the if can be simplified to if (pluginType.IsAssignableTo( typeof(IUpgradableDataSource))) { and your dictionary cache could be Dictionary<Type, Func<JsonElement, CancellationToken, Task<JsonElement>>>
@Charlieface it should probably be made a s_helperGenericMethod static field actually. Agreed about the other, but didn't want to change the code too too much for OP.
Thanks for pointing out that with the original approach I am unable to detect explicitly implemented interface methods. You are right that working with instance methods is much easier and so I decided to switch back and use them although I don't really need an instance for the purpose of UpgradeSourceConfiguration.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.