2

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

bturner1273
  • 660
  • 7
  • 18

2 Answers2

2

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);
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I understand what you are saying but how do I get around casting from the Task<> then? – bturner1273 Sep 04 '20 at 20:58
  • 1
    It might be interesting to mention the difference when using the `await` operator to consume the task. i.e. `async Task() => await Task.FromResult(null);`. The difference is intuitive and and ameliorates the invariance of `Task` in many practical scenarios. – Aluan Haddad Sep 04 '20 at 21:02
  • @bturner1273 was glad to help! Also would recommend to cache reflection somehow cause it can be quite expensive. – Guru Stron Sep 04 '20 at 21:17
0

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

public async Task<ModuleInfoResponse> GetModuleInfo()

change to :

async Task<IWebSocketResponseData> GetModuleInfo()
  • yeah issue is then I can't reflect to find the proper method :'( – bturner1273 Sep 04 '20 at 20:55
  • 1
    Very true this only address your invalid cast problem. An option for using reflection would be to assign a custom attribute to GetModuleInfo and GetNetworkSettings and check the attribute value. Your CheckBoxData seems to be related by "modinfo" vs. "network" so perhaps those are candidate values for an attribute. – Jim Sebring Sep 04 '20 at 22:04