C# method invocation not able to cast classes implementing interface to said interface

Viewed 86

So I have an app that sends websocket requests to a server and records the responses for later viewing. I would like the user to be able to check off the responses they want to record. I have methods to get all the different response types and each response type implements IWebSocketResponseData. Then I have a method that returns all of the response types based on the CheckBoxData list passed in, it gets all the responses that are checked off from the server and should return a List<IWebSocketResponseData>.

public async Task<List<IWebSocketResponseData>> GetData(List<CheckBoxData> dataToGet)
        {
            var toReturn = new List<IWebSocketResponseData>();
            foreach (var data in dataToGet)
            {
                var method = GetType()
                    .GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
                    .SingleOrDefault(m => m.ReturnType.GenericTypeArguments.Contains(data.ResponseType));
                IWebSocketResponseData response = await (Task<IWebSocketResponseData>)method.Invoke(this, null);
                toReturn.Add(response);
            }
            return toReturn;
        }

        public async Task<ModuleInfoResponse> GetModuleInfo()
        {
            await SendAsync(JsonConvert.SerializeObject(new
            {
                type = "fetch",
                item = "modinfo"
            }));
            return await ReceiveAsync<ModuleInfoResponse>();
        }

        public async Task<NetworkSettingsResponse> GetNetworkSettings()
        {
            await SendAsync(JsonConvert.SerializeObject(new
            {
                type = "fetch",
                item = "network"
            }));
            return await ReceiveAsync<NetworkSettingsResponse>();
        }

The two methods below GetData are examples of the methods that would be Invoked from the GetData method. Keep in mind ModuleInfoResponse and NetworkSettingsResponse both implement IWebSocketDataResponse which is just a label basically i.e.

public interface IWebSocketDataResponse { }

This gives me an InvalidCastException trying to cast from any of the concrete response classes i.e. ModuleInfoResponse, NetworkSettingsResponse... Why?

Edit: I realize there is no null check on method cause I know I am gonna get some heat for that

2 Answers

This is because Task is not covariant. In C# variance can only be applied to generic interfaces (and delegates), so you can do next:

public interface IWebSocketDataResponse { }
public class ModuleInfoResponse : IWebSocketDataResponse { }
interface IFoo<out T> // or just interface IFoo<out T> {} for testing purposes 
{ 
    T GetSomething();
    // The following statement generates a compiler error.
    // void SetSomething(T sampleArg);
}

IFoo<ModuleInfoResponse> iX = ...;
IFoo<IWebSocketDataResponse> IXX = iX;

But can't:

Task<IWebSocketDataResponse> x = Task.FromResult<ModuleInfoResponse>(null);

Also please read this answer.

UPD

One of the options would be to continue using refection, something like this:

var task = (Task)method.Invoke(this, null);
await task;
var response = (IWebSocketResponseData)task.GetType().GetProperty("Result").GetValue(task);

Both GetModuleInfo and GetNetworkSettings should be changed to return the interface:

public async Task<ModuleInfoResponse> GetModuleInfo()

change to :

async Task<IWebSocketResponseData> GetModuleInfo()
Related