我可以定义两个名称相同但参数不同的委托吗?

6
我试图定义一个 Int32IntPtr 之间的委托重载。为什么下面的重载是非法的?
public delegate int EnumWindowsCallback (System.IntPtr hWnd, int lParam);

public delegate int EnumWindowsCallback (System.IntPtr hWnd, System.IntPtr lParam);

看起来很奇怪。它们都是结构体,但不同,并且从不同的接口实现。

想一想,我以前从未尝试过重载委托。这甚至是合法的吗?如果是,为什么?

更新:浏览答案和其他SO帖子后,我很惊讶委托甚至不能声明具有不同数量的参数。我仍然想知道为什么这不能在运行时解决。


既然你似乎还对此感到困惑,为什么不尝试制定一个完整的示例,如果存在重载委托,则可以编译。确保实现委托使用的两个方面(即将委托绑定到方法或lambda,将其传递给函数,然后编写调用委托的函数)。接收委托的函数是否适用于委托的所有重载?在调用委托时,它应该如何知道参数类型和参数数量是多少? - Ben Voigt
1
可能是C# - 如何“重载”委托?的重复问题。 - nawfal
4个回答

11

想想看,我从来没有尝试过重载一个委托。这合法吗?如果是,为什么呢?

不,这是不合法的。您当前声明了两个具有相同完全限定名称的类型。

唯一看起来有点像类型重载的事情是,如果您声明两种在通用类型参数数量上不同的类型,例如Action<T>Action<T1, T2>等。对于委托的规则与此处其他类型的规则相同。

因此,您需要声明一个通用委托(并使用不同的类型参数),或者使用两个不同的类型名称。


1
@RaheelKhan:当你声明一个委托类型时,实际上是在声明一个从MulticastDelegate派生的新类,并由编译器添加了一些额外成员。你不能声明Action<T>Action<U>,因为它们具有相同数量的泛型类型参数。你可以声明Action<T>Action<T1, T2>,因为一个有一个泛型类型参数,另一个有两个。 - Jon Skeet
只是确认一下我的疑惑的原因,你似乎既不能在运行时解决这个问题,也不能在编译时解决。对吗? - Raheel Khan
1
@RaheelKhan:很难理解你的意思——它在编译时失败,因此我们无法在执行时讨论任何情况。你不能在同一程序集中使用相同的完全限定名称和相同数量的泛型类型参数声明两种类型。 - Jon Skeet
2
@RaheelKhan:嗯,你必须提出一种具体的方法来允许同名的多个类型同时存在,或者采用某种名称混合方法为每个委托分配不同的名称。这两种方法都可能在CLR、BCL和语言方面产生非常重大的影响……远远超出了我现在能够处理的范围,说实话。并不像“创建委托实例”是需要完成的唯一任务。虽然这样的方案可能是可行的,但我认为其缺点会超过优点。 - Jon Skeet
我的意图显然不是要提出建议 :)非常感谢您的耐心回答我实际想要知道的内容。正是您最后一条评论让我真正理解了委托作为一种类型。 - Raheel Khan
显示剩余3条评论

6

不,您不能重载委托。当编译器有类型信息可供选择时,会选择重载...但是对于委托,您正在提供类型信息,编译器无法从重载中进行选择。

如果您想要一组类似的委托类型,可以使用泛型。

public delegate int EnumWindowsCallback<LParamType>(System.IntPtr hWnd, LParamType lParam);

现在您可以定义重载的p / invoke签名,接受不同的委托类型EnumWindowsCallback<int>EnumWindowsCallback<IntPtr>等。

谢谢Ben。这很有道理,看了你和Jon的回答后,我正在努力理解为什么这不能在运行时解决。请看下面对答案的评论。 - Raheel Khan
@RaheelKhan:区别在于必须在使用时命名实际使用的类型。您不能使用EnumWindowsCallback定义p/invoke签名,让编译器自动确定LParamType是什么。 - Ben Voigt
请谅解我的无知,但如果我声明底层函数(如果需要的话)是否会有所不同呢? - Raheel Khan
2
@RaheelKhan:不同之处在于你不是声明“函数”-你是声明“类型”。 - Jon Skeet
是的,你可以,见下面我的回答。我最初的意图是从一个泛型类中调用一个非泛型方法。关于这个问题的帖子有非常奇怪的解决方案,所以我自己写了一个。 - Tobias Knauss

2
所有委托类型都只能包含一个.Invoke方法。如果使用CIL定义了一个从Delegate派生并包含多个Invoke重载的类型,我不确定框架会做什么,但是只存在一个Invoke方法的期望已经被很好地融入到了框架中。
然而,可能可以定义一个接口来代替委托类型。例如,可以定义如下内容:
interface IInvokableAsOptionalGeneric
{
  void Invoke();
  void Invoke<T>(T param);
}

在这种情况下,具有对实现了InvokableAsOptionalGeneric的某些内容的引用的代码可以不带参数调用它,也可以使用任何类型的参数调用它;后者可以用于值类型参数而不需要装箱(而Action<Object>则必须对参数进行装箱)。请注意,对于上述样式的任何接口,都可以定义一个类,其静态方法类似于Delegate.Combine,可与实现该接口的任何对象一起使用;尽管大部分代码都是样板文件,但每个这样的接口都需要自己的“组合”类。

谢谢!这个例子对我来说比仅仅将委托视为类型更有意义。这种知识对于那些尚未掌握平台但可以进行良好推理的普通开发人员来说真的非常有益。 - Raheel Khan
1
@RaheelKhan:很高兴它有帮助。使用委托与接口的一个区别在于,当使用接口方法时,每个可以由给定接口调用的不同方法必须位于不同的类中,而一个委托类可以调用任意数量的方法。另一方面,即使一个堆对象可以实现任意数量的不同接口,其方法具有任何组合的签名,委托也必须附加到单个方法(具有单个签名)。 - supercat

2

我不喜欢那些总是说“不行”的人。;-)
因此我的答案是:可以!

起初我想从一个泛型方法中调用一个重载的非泛型方法。编译器不喜欢这样做。可能的解决方案在SO 5666004SO 3905398中,但我发现它们相当复杂。

在阅读了这篇文章和其他文章之后,我在脑海中有了一些模糊的想法。试错和学习新函数让我找到了一个可行的解决方案。

其他人是正确的,你不能重载普通委托,因为每个委托都有自己的类型并使用静态绑定。
但是你可以使用抽象的Delegate类和动态绑定。

下面是一个可编译和运行的解决方案(用C++/CLI编写):

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Threading;

delegate void DelegateVI (int);
delegate void DelegateVB (bool);
delegate void DelegateVAUC (array<unsigned char>^);

ref class CWorker
{
public:
  void DoWork (int i_iValue)
  {
    Console::WriteLine ("int");
    Thread::Sleep (500);
  }

  void DoWork (bool i_bValue)
  {
    Console::WriteLine ("bool");
    Thread::Sleep (1000);
  }

  void DoWork (array<unsigned char>^ i_aucValue)
  {
    Console::WriteLine ("array<uc>");
    Thread::Sleep (2000);
  }
};

generic <class T>
ref class CData
{
public:
  CData (int i_iSize, CWorker^ i_oWorker)
  {
    m_aData = gcnew array<T>(i_iSize);
    if (T::typeid == int::typeid)
    {
      Reflection::MethodInfo^ oMethod = CWorker::typeid->GetMethod("DoWork", gcnew array<Type^>{int::typeid});
      m_delDoWork = Delegate::CreateDelegate (DelegateVI::typeid, i_oWorker, oMethod);
    }
    else if (T::typeid == bool::typeid)
    {
      Reflection::MethodInfo^ oMethod = CWorker::typeid->GetMethod("DoWork", gcnew array<Type^>{bool::typeid});
      m_delDoWork = Delegate::CreateDelegate (DelegateVB::typeid, i_oWorker, oMethod);
    }
    if (T::typeid == array<unsigned char>::typeid)
    {
      Reflection::MethodInfo^ oMethod = CWorker::typeid->GetMethod("DoWork", gcnew array<Type^>{array<unsigned char>::typeid});
      m_delDoWork = Delegate::CreateDelegate (DelegateVAUC::typeid, i_oWorker, oMethod);
    }
  }

  void DoWork (CWorker^ i_oWorker)
  {
    m_delDoWork->DynamicInvoke (gcnew array<Object^>{m_aData[0]});
    // i_oWorker->DoWork (m_aData[0]);  //--> fails with compiler error C2664: cannot convert argument...
  }

  array<T>^ m_aData;
  Delegate^ m_delDoWork;
};

int main()
{
  CWorker^ oWorker = gcnew CWorker;
  CData<bool>^ oData = gcnew CData<bool>(3, oWorker);
  oData->DoWork (oWorker);
}

1
嗯,不喜欢人并不是最好的态度。挑战想法是可以的,但要沿着“这是不可能的,还是你不知道如何做”这样的思路进行。但在这种情况下,确实是不可能的。您使用的非特定 System.Delegate 与 p/invoke 不兼容,而这正是此问题的应用领域。更糟糕的是,在调用 CreateDelegate 之前,您选择了一个单一类型,并且您得到的委托仅接受一个参数签名。您已经“重载了委托的创建”,但没有“创建重载的委托”。 - Ben Voigt
@Ben Voigt:请注意那句话后面的笑脸。//是的,你说得对。重载委托不存在,但根据我的了解,这是你可以得到的最接近的东西。如果可以这么说,这是伪重载。顺便说一句,我永远不会质疑你比我有更丰富的经验。 - Tobias Knauss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接