【问题标题】:C# method invocation not able to cast classes implementing interface to said interfaceC# 方法调用无法将实现接口的类转换为所述接口
【发布时间】:2020-12-24 02:37:59
【问题描述】:

所以我有一个应用程序将 websocket 请求发送到服务器并记录响应以供以后查看。我希望用户能够检查他们想要记录的响应。我有获取所有不同响应类型的方法,并且每种响应类型都实现了 IWebSocketResponseData。然后我有一个方法可以根据传入的 CheckBoxData 列表返回所有响应类型,它会从服务器获取所有已检查的响应,并且应该返回 List

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>();
        }

GetData 下面的两个方法是将从 GetData 方法调用的方法的示例。请记住 ModuleInfoResponse 和 NetworkSettingsResponse 都实现了 IWebSocketDataResponse 这基本上只是一个标签,即

public interface IWebSocketDataResponse { }

这给了我一个 InvalidCastException 尝试从任何具体响应类(即 ModuleInfoResponse、NetworkSettingsResponse...)进行转换...为什么?

编辑:我意识到方法没有空检查,因为我知道我会为此得到一些热量

【问题讨论】:

    标签: c# .net polymorphism


    【解决方案1】:

    这是因为Task 不是协变的。在 C# 中,variance 只能应用于泛型接口(和委托),所以接下来可以这样做:

    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;
    

    但不能:

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

    也请阅读此answer

    UPD

    其中一个选项是继续使用反射,如下所示:

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

    【讨论】:

    • 我明白你在说什么,但是我该如何避免从任务中进行投射呢?
    • 在使用await 操作符来消费任务时,提到区别可能会很有趣。即async Task&lt;IWebSocketDataResponse&gt;() =&gt; await Task.FromResult&lt;ModuleInfoResponse&gt;(null);。区别很直观,并且改善了Task&lt;T&gt; 在许多实际场景中的不变性。
    • @bturner1273 很高兴为您提供帮助!还建议以某种方式缓存反射,因为它可能非常昂贵。
    【解决方案2】:

    GetModuleInfo 和 GetNetworkSettings 都应该改成返回接口:

    public async Task<ModuleInfoResponse> GetModuleInfo()
    

    改为:

    async Task<IWebSocketResponseData> GetModuleInfo()
    

    【讨论】:

    • 是的问题是我无法反思以找到合适的方法:'(
    • 非常正确,这仅解决了您的无效演员表问题。使用反射的一个选项是将自定义属性分配给 GetModuleInfo 和 GetNetworkSettings 并检查属性值。您的 CheckBoxData 似乎与“modinfo”与“network”相关,所以这些可能是属性的候选值。
    猜你喜欢
    • 2013-10-04
    • 2019-05-08
    • 1970-01-01
    • 1970-01-01
    • 2014-12-10
    • 2015-08-31
    • 2019-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多