【问题标题】:Overloading function call operator in C#在 C# 中重载函数调用运算符
【发布时间】:2011-01-27 20:29:48
【问题描述】:

是否可以重载 C# 中的默认函数运算符(() 运算符)?如果是这样 - 如何?如果没有,是否有一种解决方法可以产生类似的影响?

谢谢,
阿萨夫

编辑:
我试图给一个类一个默认的操作符,类似于:

class A {
    A(int myvalue) {/*save value*/}

    public static int operator() (A a) {return a.val;}
    ....
   }

...
A a = new A(5);
Console.Write(A());

编辑 2:
我已阅读规范,并且了解没有直截了当的方法可以做到这一点。我希望有一个解决方法。

编辑 3: 动机是让一个类或一个实例表现得像一个函数,以创建一个方便的日志接口。顺便说一句,这在 C++ 中是可行且合理的。

【问题讨论】:

  • 在 C++ 中不要混淆“可行”和“合理”
  • @Isak 恕我直言,我认为我不是。
  • @Isak 不要将“在 C++ 中可行但在 C# 中不可行”与“不合理”混淆 ;-)
  • +1 C# 中的对象无法像函数一样工作,这很烦人。具体来说,与 C++ 不同,让变量表现得像一组函数重载是非常混乱的(转发委托采用 params object[])。
  • 嗨@AsafR 我知道这是旧的,但如果我可以假设你的意思是a 而不是AConsole.Write 那么我想我有一个答案给你。

标签: c# operator-overloading


【解决方案1】:

不,() 不是运算符,因此不能重载。这是不正确的,请参阅下面 Eric Lippert 的评论)括号是C# 的语法,用于表达一组传递给方法的参数。 () 只是表示所讨论的方法没有指定形式参数,因此不需要参数。

你想做什么?或许如果您给出一个问题的小例子,我们将能够提供解决方案。

编辑:好的,我知道你现在得到了什么。您总是可以像这样创建一个映射到实例上的方法的委托(假设class A 定义了这样的方法:public void Foo() { }):

Action action = someA.Foo;

然后您可以使用如下简单语法调用委托:

action();

不幸的是(或不是,取决于您的偏好)这几乎与 C# 允许您使用这种语法一样接近。

【讨论】:

  • 函数调用运算符最肯定是运算符。它不是一个可重载的运算符,但它绝对是一个运算符;它有操作数,它有运算符优先级,它有关联性,它被记录为一个运算符,被解析为一个运算符并被编码为一个运算符。
【解决方案2】:

没有。 C# specification 的第 7.2.2 节将可重载运算符定义为:

一元:+ - ! ~ ++ -- 真 错误
二进制:+ - * / % & | ^ > == != > =

不管怎样,你的可读性会很糟糕。也许还有另一种方法可以实现您想要做的事情?

【讨论】:

  • -1 表示完全主观的陈述,即可读性会一团糟。可读性是想要这个的唯一原因,因为这总是可以通过更复杂的方式来实现。必须做treatMeAsAFunction.Invoke() 并不比treatMeAsAFunction() 更具可读性,除非您认为所有函数都应该以这种方式调用。如果某物代表一个函数,那么像函数一样可调用是有意义的。
【解决方案3】:

你不能重载(),但你可以从它的一个方法中取出一个对象并创建一个委托(类似于函数指针):

class Foo
{ 
    private Bar bar;
    // etc.

    public Baz DoSomething(int n)
    {
        // do something, for instance, 
        // get a Baz somehow from the 'bar' field and your int 'n'
        // ...
    }
}

现在,DoSomething 是一个接受 int 并返回 Baz 的方法,它与委托类型 Func<int, Baz> 兼容。
(有 ActionAction<T> 用于返回 void 的方法,并分别接受不接受或接受一个参数。还有 Func<T1, T2, TResult> 和接受更多参数的变体。)

所以你可以有一个方法接受Func<int, Baz>,然后用它做任何事情:

void Frob(Func<int, Baz> f)
{
        Baz b = f(5); 
        // do whatever with your baz
}

最后,创建委托并将其传递给Frob,如下所示:

Foo foo = new Foo();
Func<int, Baz> f = new Func<int, Baz>(foo.DoSomething);
Frob(f);

这有什么帮助吗?我仍然不清楚你到底想要完成什么。

【讨论】:

    【解决方案4】:

    您提到您想要进行日志记录,以下是您可以通过委托执行此操作的方法:

    FooBar MyAlgorithm(Foo paramA, Bar paramB, Actions<string> logger) {
        logger("Starting algorithm.")
        FooBar result = ...;
        logger("Finished algorithm.");
        return result;
    }
    

    当你运行它时,你可以登录到控制台:

    MyAlgorithm(a, b, (text) => Console.WriteLine(text));
    

    或登录到 Windows 窗体文本框:

    TextBox logbox = form.GetLogTextBox();
    MyAlgorithm(a, b, (text) => logbox.Text += text + Environment.NewLine);
    

    【讨论】:

      【解决方案5】:

      查看implicit conversion。另一个选项是explict conversion,但是您需要转换对象类型。

      public class A
      {
          public A(int myValue)
          {
              this.MyValue = myValue;
          }
          public int MyValue { get; private set; }
      
          public static implicit operator int(A a)
          {
              return a.MyValue;
          }
          public static implicit operator A(int value)
          {
              return new A(value);
          }
          // you would need to override .ToString() for Console.WriteLine to notice.
          public override string ToString()
          {
              return this.MyValue.ToString();
          }
      }
      class Program
      {
          static void Main(string[] args)
          {
              A t = 5;
              int r = t;
              Console.WriteLine(t); // 5
          }
      }
      

      【讨论】:

        【解决方案6】:

        是否可以重载 C# 中的默认函数运算符(() 运算符)?如果是 - 如何?

        在 C# 中,只有方法和委托才能作为函数调用。在 C# 中,委托与您所询问的 C++ function objects 一样接近。

        如果没有,是否有解决方法来产生类似的影响?

        你不能得到你想要的,但你可以模糊地接近(语法方面)。

        使用重载的转换运算符

        定义:

        public static implicit operator DelegateType(TypeToConvert value)
        {
            return value.TheMethodToCall;
        }
        

        用法:

        var someValue = new TypeToConvert();
        DelegateType someValueFunc = someValue;
        someValueFunc(value1);
        

        这将获得您想要的最终语法,但需要您在指定类型的地方进行中间转换。

        您可以这样做:

        • 将值分配给局部变量,或将其传递给采用匹配委托类型的函数(隐式转换)
        • 强制转换(显式转换)

        使用索引器

        定义:

        public DelegateType this[ParameterType1 value1, ...]
        {
            get
            {
                // DelegateType would take no parameters, but might return a value
                return () => TheMethodToCall(value1);
            }
        }
        

        用法:

        variable[value1]();
        

        索引器版本的语法看起来很有趣,但原始问题中的示例也是如此(使用标准 C# 习惯用法)。它也受到限制,因为您无法定义采用零参数的索引器。

        如果您想要一个无参数函数,一种解决方法是创建一个虚拟参数(可能是object 类型)并将一个丢弃值传递给它(可能是null)。但这个解决方案真的很恶心,需要你深入了解它的用法。如果您想要一个采用虚拟类型的单个参数的重载,它也会中断。

        动机是让一个类或一个实例表现得像一个函数,以制作一个方便的日志接口

        考虑到这个动机,我可能会建议你放弃上面的那些选项。他们在这个问题上太过分了。如果您扩大允许的解决方案,您可能会发现您更喜欢其中之一。

        改用动态

        我提到的其他方法需要强类型,绝不是通用的。对于您所描述的内容,这可能是一个巨大的劣势。

        如果你想要更弱的绑定,你可以查看Dynamic。这将要求您调用命名方法,并且不允许您尝试实现的短语法。但它会松散地绑定,并且可以优雅地失败。

        改用更简单的 .Net 功能

        您还可以研究其他解决方案。

        接口:

        使用标准化方法创建一个基本的ILoggable 接口。

        扩展方法:

        使用.Log() extension methods 创建您的日志接口。扩展方法可以是通用的,并且可以采用基本类型,例如object,因此您不必修改现有的类来支持它。

        覆盖ToString

        记录意味着您正在尝试将数据转换为文本(以便记录)。考虑到这一点,您可以简单地覆盖 ToString 方法。

        在所有这些情况下,您都可以创建方法重载,但它们将被强绑定到每种类型。但是,您在原始问题中请求的解决方案也与类型密切相关,因此这些解决方案并不是真正处于劣势。

        现有解决方案

        我见过的现有 .Net 日志库依赖于您覆盖 ToString 运算符。正如我上面所说,这是有道理的,因为您的日志是文本的。

        有关 .Net 日志记录的先前技术,请参阅以下库:

        关于内置委托类型的注意事项

        确保在所有这些情况下都使用built-in delegate types,而不是定义自己的委托类型。最终会减少混乱,并且需要您编写更少的代码。

        // function that returns void
        Action a1 = ...;
        Action<TParameter1> a2 = ...;
        Action<TParameter1, TParameter2> a3 = ...;
        // etc
        
        // function that returns a value
        Func<TReturn> f1 = ...;
        Func<TParameter1, TReturn> f2 = ...;
        Func<TParameter1, TParameter2, TReturn> f3 = ...;
        // etc
        

        【讨论】:

          【解决方案7】:

          是的,这绝对可以通过 dynamic 类型来完成(更多信息可在 here 找到)。

          using System.Dynamic.DynamicObject
          
          class A : DynamicObject 
          {
              private int val;
          
              A(int myvalue)
              {
                  this.val = myvalue;
              }
          
              public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) {
                  result = this.val;
                  return true;
              }
          }
          
          ...
          
          dynamic a = new A(5);
          Console.Write(a());
          

          dynamic 类型意味着该类型完全在运行时假定,从而为几乎与对象的每次交互提供更大的灵活性。

          我假设您打算在 Console.Write 行中使用 a 而不是 A

          【讨论】:

          • 我猜你是想说this.val = myvalue;
          【解决方案8】:

          我认为 动态 是一种解决方案。恕我直言,这不是最好的方法。

          1. 您失去了静态语法/类型检查。 在编译时不会发现拼写错误,而是会出现运行时异常。 如果不信任您的编译器,或者您更喜欢出现随机错误 在部署之后而不是之前,使用 JavaScript
          2. 动态 有性能损失,但这通常无关紧要。
          3. dynamic 函数的返回值也是 dynamic 的,而且很容易混淆(至少对我的小脑袋来说)
          4. 其他我没有提到但可能很重要的原因。

          【讨论】:

            猜你喜欢
            • 2013-02-06
            • 1970-01-01
            • 1970-01-01
            • 2011-11-09
            • 1970-01-01
            • 1970-01-01
            • 2017-03-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多