C#:自定义委托,但给出了第一个参数

C#: Custom Delegate but the first parameter is given

提问人:Warstek 提问时间:2/7/2023 最后编辑:Warstek 更新时间:2/8/2023 访问量:146

问:

我想存储来自客户端的 WebSocket 调用的回调。 我想告诉IDE第一个参数应该始终是。ClientMetadata

问题是,我想注册具有无限数量和未知类型参数的方法,但第一个必须是 ClientMetadata 对象。

我想像这样注册回调:

RegisterEventHandler("callback1", (ClientMetadata client, string smth1, int smth2);
RegisterEventHandler("callback2", (ClientMetadata client, bool smth3);

我有一个方法来存储回调:

public static void RegisterEventHandler(string identifier, Delegate callback)
{
    callbacks.Add(identifier, callback);
}

以下是回调的存储位置:

private static Dictionary<string, EventHandlerCallback> callbacks = new Dictionary<string, EventHandlerCallback>();

以下是存储方法的调用方式:

private static void Server_MessageReceive2d(object? sender, MessageReceivedEventArgs e)
{
    string jsonObj = Encoding.UTF8.GetString(e.Data);

    if (jsonObj != null)
    {
        WSMessage obj = JsonConvert.DeserializeObject<WSMessage>(jsonObj);

        if (obj == null || obj.message == null)
        {
            return;
        }
        //obj.identifier is the id of the callback in the serverside
        if (callbacks.ContainsKey(obj.identifier))
        {
            MethodInfo mi = callbacks[obj.identifier].GetMethodInfo();

            if (obj.callbackId != null && mi.ReturnType.ToString() != "System.Void")
            {
                //e.Client --> ClientMetaData
                //obj.message --> dynamic[]?
                var ret = callbacks[obj.identifier].DynamicInvoke(e.Client, obj.message);
                
                //obj.callbackId is the callback to call when the method ended
                if (ret != null)
                {
                    SendMessage(e.Client, obj.callbackId, ret);
                }
                else
                {
                    SendMessage(e.Client, obj.callbackId);
                }
            }
            else
            {
                callbacks[obj.identifier].DynamicInvoke(e.Client, obj.message);
            }
        }
        else
        {
            return;
        }
    }
}

我尝试制作一个自定义委托:

delegate void EventHandlerCallback(ClientMetadata clientMetadata, params object[] data);

事实证明,在这种情况下,关键字绝对没有任何作用。无论我使用与否,我的 IDE 都期望这样做:paramsparams

RegisterEventHandler("callback2", (ClientMetadata client, object[] smthObj);
C# 回调 委托 NET-7.0

评论

1赞 Johnathan Barclay 2/7/2023
如何调用其中一个回调?
0赞 Warstek 2/7/2023
感谢您的快速回复,调用代码段在编辑的帖子中。
0赞 Servy 2/7/2023
在调用代码中,您只传递两个参数,这两个参数都是静态已知类型。没有动态数量的参数,也不涉及任何未知类型。
0赞 Selvin 2/7/2023
您可以在内部进行运行时检查,例如...我没有看到在不编写代码分析器的情况下在编译时静态执行此操作的方法RegisterEventHandlerif(callback.Method.GetParameters()[0].ParameterType != typeof(ClientMetadata)) throw new ArgumentException("Wrong delegate");
0赞 Warstek 2/7/2023
我对此很艰难,但是有没有办法告诉我的IDE第一个参数应该始终是?ClientMetadata

答:

0赞 Aljaz Brodar 2/7/2023 #1

可以创建一个委托,该委托将 ClientMetadata 对象作为第一个参数,将对象数组作为第二个参数。这样,您可以存储包含任意数量的未知参数的回调:

delegate void EventHandlerCallback(ClientMetadata clientMetadata, object[] data);

public static void RegisterEventHandler(string identifier, 
EventHandlerCallback callback)
{
    callbacks.Add(identifier, callback);
}

用法:

RegisterEventHandler("callback1", (ClientMetadata client, object[] args) => 
{
    string smth1 = (string)args[0];
    int smth2 = (int)args[1];
});

RegisterEventHandler("callback2", (ClientMetadata client, object[] args) => 
{
    bool smth3 = (bool)args[0];
});

评论

0赞 Warstek 2/7/2023
这个解决方案最终会很好,但是当你编写一个事件处理程序的脚本时,它有点难以使用。我想像这样独立声明参数:RegisterEventHandler("callback1", (ClientMetadata client, string smth1, int smth2);
-2赞 Selvin 2/8/2023 #2

你可以去代码分析器...像这样的东西应该可以解决问题:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace AnalyzerTest
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class AnalyzerTestAnalyzer : DiagnosticAnalyzer
    {
        public const string ExpectedParameterType = "ClientMetadata";
        public const string MethodName = "RegisterEventHandler";

        public const string DiagnosticId = "SEL001";
        public const string Title = "AnalyzerTest";
        public const string MessageFormat = "Method call '{0}' wrong argument. First parameter of delegate should be '{1}'";
        public const string Category = "Error";

        private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
            context.EnableConcurrentExecution();
            context.RegisterSyntaxNodeAction(NodeAction, new SyntaxKind[] { SyntaxKind.InvocationExpression });
        }

        private void NodeAction(SyntaxNodeAnalysisContext context)
        {
            var registerMethodSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(context.Node).Symbol;
            if(registerMethodSymbol.Name == MethodName)
            {
                IMethodSymbol methodSymbol;
                var callbackSyntax = ((InvocationExpressionSyntax)context.Node).ArgumentList.Arguments[1];
                var callbackTypeInfo = context.SemanticModel.GetTypeInfo(callbackSyntax.Expression);
                if(callbackTypeInfo.Type != null)
                {
                    var namedType = callbackTypeInfo.Type as INamedTypeSymbol;
                    //Action<ClientMetadata, ...> or Func<ClientMetadata, ..., ret> or delegate
                    methodSymbol = namedType.DelegateInvokeMethod;
                }
                else
                {
                    //Method
                    var symbolInfo = context.SemanticModel.GetSymbolInfo(callbackSyntax.Expression);
                    methodSymbol = (symbolInfo.Symbol ?? (symbolInfo.CandidateSymbols != null ? symbolInfo.CandidateSymbols[0] : null)) as IMethodSymbol;

                }
                if(methodSymbol != null && methodSymbol.Parameters.Length > 0 && methodSymbol.Parameters[0].Type.Name == ExpectedParameterType)
                {
                    return;
                }
                var diagnostic = Diagnostic.Create(Rule, callbackSyntax.GetLocation(), MethodName, ExpectedParameterType);
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}

以下是它的工作原理示例(当然它会导致编译时错误)Example

评论

0赞 Warstek 6/5/2023
您好,对不起,我的回复晚了。我最近没有太多时间做这个项目。你能把完整的代码发给我吗?对不起,我不明白我应该如何实现您的解决方案。