Overloading function call operator in C#

2020-01-31 02:39发布

问题:

Is it possible to overload the default function operator (the () operator) in C#? If so - how? If not, is there a workaround to create a similar affect?

Thanks,
Asaf

EDIT:
I'm trying to give a class a default operator, something along the lines of:

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

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

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

EDIT 2:
I've read the spec and I understand there's no straight forward way to do this. I was hoping there's a workaround.

EDIT 3: The motivation is to make a class, or an instance behave like a function, to make a convenient logging interface. By the way, this is doable and reasonable in C++.

回答1:

No, () isn't an operator so it cannot be overloaded. (This is incorrect, please see Eric Lippert's comment below) The parentheses are part of C#'s syntax that are used to express a set of arguments that are passed to a method. () simply indicates that the method in question specified no formal parameters and therefore requires no arguments.

What are you trying to do? Perhaps if you gave a small example of the problem we would be able to help with a solution.

Edit: Okay I see what you are getting at now. You could always create a delegate that maps to the method on the instance like this (given that class A defines a method like this: public void Foo() { }):

Action action = someA.Foo;

Then you could invoke the delegate with a simple syntax like this:

action();

Unfortunately (or not, depending on your preference) this is pretty much as close as C# will let you get to this kind of syntax.



回答2:

There is not. Section 7.2.2 of the C# specification defines the overloadable operators as:

UNARY: + - ! ~ ++ -- true false
BINARY: + - * / % & | ^ << >> == != > < >= <=

Your readability would go to all hell anyway. Maybe there's another way to achieve what you're trying to do?



回答3:

Is it possible to overload the default function operator (the () operator) in C#? If so - how?

In C#, only methods and delegates make sense to invoke as functions. In C#, delegates are as close as you get to the C++ function objects you are asking about.

If not, is there a workaround to create a similar affect?

You can't get exactly what you want, but you can get vaguely close (syntax-wise).

Using overloaded conversion operators

Definition:

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

Usage:

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

This gets the final syntax you want, but requires you to do an intermediate conversion where you specify the type.

You can do this by:

  • Assigning your value to a local variable, or passing it to a function taking a matching delegate type (implicit conversion)
  • Casting it (explicit conversion)

Using indexers

Definition:

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

Usage:

variable[value1]();

The indexer version has funny-looking syntax, but so does the example in your original question (wrt the standard C# idioms). It is also limited, because you can't define an indexer that takes zero parameters.

A work-around if you want a no-parameter function is to make a dummy parameter (probably of type object) and passing a throw-away value to it (probably null). But that solution is really gross, and requires you to look under the hood to understand the usage. It would also break if you ever wanted an overload that took a single parameter of your dummy type.

The motivation is to make a class, or an instance behave like a function, to make a convenient logging interface

With this motivation in mind, I might suggest you abandon those options above. They are overkill for this problem. If you broaden your allowed solutions, you may find you like one of them better.

Using dynamic instead

The other methods I mentioned require strong typing, and are in no way generic. This may be a huge disadvantage for what you are describing.

If you want weaker binding, you could look into Dynamic. This would require you to invoke named methods, and wouldn't allow the short syntax you're trying to implement. But it would be loosely bound, and could fail gracefully.

Using simpler .Net features instead

There are other solutions you could look into.

Interfaces:

Create a base ILoggable interface, with standardized methods.

Extension methods:

Create your logging interface with .Log() extension methods. Extension methods can be made generic, and can take base types, like object, so you wouldn't have to modify your existing classes to support this.

Override ToString:

Logging implies that you are trying to convert your data into text (so it can be logged). With this in mind, you could simply override the ToString method.

You can create method overloads in all these cases, but they will be strongly bound to each type. The solution you requested in your original question also is strongly bound to the type, though, so these solutions aren't really at a disadvantage.

Existing solutions

The existing .Net logging libraries I've seen rely on you overriding the ToString operator. As I said above, this makes sense, because your log is textual.

For previous art on .Net logging, see these libraries:

  • http://logging.apache.org/log4net/
  • http://nlog-project.org/
  • http://docs.castleproject.org/Windsor.Logging-Facility.ashx (a generic logger)

Note about built-in delegate types

Make sure you use the built-in delegate types in all these cases, instead of defining your own delegate types. It will be less confusing in the end, and require you to write less code.

// 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


回答4:

Yes, this can be absolutely be done with the dynamic type (more info found 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());

The dynamic type means that the type is assumed entirely at runtime allowing for a greater flexability to almost every interaction with the object.

I have assumed you meant to use a rather than A in the Console.Write line.



回答5:

You can't overload (), but you can take an object and make a delegate (similar to a function pointer) out of one of its methods:

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'
        // ...
    }
}

Now, DoSomething is a method that takes an int and returns a Baz, which is compatible with delegate type Func<int, Baz>.
(There's Action and Action<T> for methods that return void, and take, respectively, no or one argument. There's also Func<T1, T2, TResult> and variants for accepting more arguments.)

So you could have a method that takes a Func<int, Baz>, and does whatever with it:

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

Finally, creating the delegate and passing it to Frob goes like this:

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

Does this help anything at all? I'm still pretty unclear on what exactly you want to accomplish.



回答6:

You mention that you want to do logging, here is how you could do that with delegates:

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

When you run this you could log to the console:

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

Or log to a Windows Forms TextBox:

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


回答7:

Check out implicit conversion. Another option would be explict conversion, but then you would need to cast the object type.

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
    }
}