Перегрузка функции вызова оператора в C # - PullRequest
26 голосов
/ 15 марта 2010

Можно ли перегрузить оператор функции по умолчанию (оператор ()) в C #? Если так - как? Если нет, есть ли обходной путь для создания аналогичного эффекта?

Спасибо
Асаф

EDIT:
Я пытаюсь дать классу оператор по умолчанию, что-то вроде:

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 ++.

Ответы [ 7 ]

17 голосов
/ 15 марта 2010

Там нет. Раздел 7.2.2 спецификации C # определяет перегружаемые операторы как:

UNARY: + -! ~ ++ - правда false
BINARY: + - * /% & | ^ << >> ==! => <> = <= </p>

Ваша читабельность все равно пошла бы в ад. Может быть, есть другой способ добиться того, что вы пытаетесь сделать?

13 голосов
/ 07 июля 2011

Возможно ли перегрузить оператор функции по умолчанию (оператор ()) в C #? Если так - как?

В C # только методы и делегаты имеют смысл вызывать как функции. В C # делегаты так же близки, как и к объектам функций C ++ , о которых вы спрашиваете.

Если нет, есть ли обходной путь для создания аналогичного эффекта?

Вы не можете получить именно то, что вы хотите, но вы можете приблизиться немного (по синтаксису).

Использование перегруженных операторов преобразования

Определение:

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() методов расширения . Методы расширения могут быть сделаны универсальными и могут принимать базовые типы, такие как object, поэтому вам не придется изменять существующие классы для поддержки этого.

Переопределить ToString:

Ведение журнала подразумевает, что вы пытаетесь преобразовать ваши данные в текст (чтобы их можно было зарегистрировать). Имея это в виду, вы можете просто переопределить метод ToString.

Вы можете создавать перегрузки методов во всех этих случаях, но они будут строго привязаны к каждому типу. Решение, которое вы запрашивали в исходном вопросе, также сильно привязано к типу, так что эти решения на самом деле не в невыгодном положении.

Существующие решения

Существующие библиотеки журналов .Net, которые я видел, полагаются на то, что вы переопределяете оператор ToString. Как я уже говорил выше, это имеет смысл, потому что ваш журнал текстовый.

Предыдущие версии журналов .Net см. В следующих библиотеках:

Примечание о встроенных типах делегатов

Убедитесь, что вы используете встроенные типы делегатов во всех этих случаях вместо определения собственных типов делегатов. Это будет менее запутанным в конце, и потребует от вас писать меньше кода.

// 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
8 голосов
/ 15 марта 2010

Нет, () не является оператором, поэтому его нельзя перегрузить. ( Это неверно, см. Комментарий Эрика Липперта ниже ) Скобки являются частью C # 's синтаксис , которые используются для выражения набора аргументов, передаваемых методу. () просто указывает, что в рассматриваемом методе не заданы формальные параметры, и поэтому не требует аргументов.

Что вы пытаетесь сделать? Возможно, если вы приведете небольшой пример проблемы, мы сможем помочь с решением.

Редактировать: Хорошо, я вижу, что вы получаете сейчас. Вы всегда можете создать делегат, который сопоставляется с методом в экземпляре следующим образом (учитывая, что class A определяет метод следующим образом: public void Foo() { }):

Action action = someA.Foo;

Тогда вы можете вызвать делегата с простым синтаксисом, подобным этому:

action();

К сожалению (или нет, в зависимости от ваших предпочтений), это почти так же близко, как C # позволит вам получить такой синтаксис.

5 голосов
/ 29 июня 2017

Да, это можно сделать с типом dynamic (более подробная информация найдена здесь ).

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 означает, что тип предполагается полностью во время выполнения, что обеспечивает большую гибкость практически при любом взаимодействии с объектом.

Я предположил, что вы хотели использовать a вместо A в строке Console.Write.

0 голосов
/ 15 марта 2010

Проверить неявное преобразование . Другим вариантом будет явное преобразование , но тогда вам нужно будет привести тип объекта.

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
    }
}
0 голосов
/ 15 марта 2010

Вы упомянули, что хотите заняться протоколированием, вот как вы можете это сделать с делегатами:

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 Forms:

TextBox logbox = form.GetLogTextBox();
MyAlgorithm(a, b, (text) => logbox.Text += text + Environment.NewLine);
0 голосов
/ 15 марта 2010

Вы не можете перегрузить (), но вы можете взять объект и сделать делегат (аналогичный указателю на функцию) из одного из его методов:

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>.
(Есть Action и Action<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);

Помогает ли это вообще? Мне все еще неясно, чего именно вы хотите достичь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...