web-dev-qa-db-ja.com

C#での関数呼び出し演算子のオーバーロード

C#でデフォルトの関数演算子(()演算子)をオーバーロードすることは可能ですか?もしそうなら-どのように?そうでない場合、同様の影響を作成するための回避策はありますか?

ありがとう、
Asaf

編集:
クラスにデフォルトの演算子を与えようとしています。これは次のようなものです。

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++では実行可能で合理的です。

28
Asaf R

いいえ、_()_は演算子ではないため、オーバーロードすることはできません。これは正しくありません。以下のEric Lippertのコメントを参照してください)括弧はC#の構文の一部ですメソッドに渡される一連の引数を表すために使用されます。 _()_は、問題のメソッドが仮パラメーターを指定していないため、引数を必要としないことを示しています。

あなたは何をしようとしているのですか?おそらく、問題の小さな例を挙げれば、私たちは解決策を手伝うことができるでしょう。

編集:さて、あなたが今何を得ているかわかります。このようなインスタンスのメソッドにマップするデリゲートをいつでも作成できます(_class A_が次のようなメソッドを定義している場合:public void Foo() { }):

_Action action = someA.Foo;
_

次に、次のような単純な構文でデリゲートを呼び出すことができます。

_action();
_

残念ながら(または、好みによってはそうではありませんが)、これはC#でこの種の構文に到達できるのとほぼ同じです。

8
Andrew Hare

存在しない。 C#仕様 のセクション7.2.2は、オーバーロード可能な演算子を次のように定義しています。

単項:+-! 〜++-真偽
BINARY:+-* /%&| ^ << >> ==!=> <> = <=

あなたの読みやすさはとにかくすべての地獄に行きます。たぶん、あなたがやろうとしていることを達成するための別の方法がありますか?

17

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)を渡すことです。しかし、その解決策は本当に粗雑であり、使用法を理解するために内部を調べる必要があります。ダミータイプの単一のパラメーターを受け取るオーバーロードが必要な場合にも、問題が発生します。

動機は、クラスまたはインスタンスを関数のように動作させて、便利なロギングインターフェイスを作成することです

この動機を念頭に置いて、上記のオプションを放棄することをお勧めします。彼らはこの問題に対してやり過ぎです。許可されているソリューションを広げると、そのうちの1つが好きになるかもしれません。

代わりに動的を使用

私が言及した他の方法は強い型付けを必要とし、決して一般的ではありません。これはあなたが説明していることにとって大きな不利益になるかもしれません。

より弱いバインディングが必要な場合は、 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
_
13

はい、これは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タイプは、タイプが実行時に完全に想定されることを意味し、オブジェクトとのほぼすべての対話に対してより大きな柔軟性を可能にします。

Console.Write行でaではなくAを使用するつもりだったと思います。

6
Tom

()をオーバーロードすることはできませんが、オブジェクトを取得して、そのメソッドの1つからデリゲート(関数ポインターと同様)を作成することはできます。

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を受け取り、デリゲート型Func<int, Baz>と互換性のあるBazを返すメソッドです。
(voidを返すメソッドにはActionAction<T>があり、それぞれ引数なしまたは1つを取ります。さらに引数を受け入れるための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);

これは何か役に立ちますか?あなたが何を達成したいのか、私はまだかなりはっきりしていません。

0
Joren

ロギングを実行したいということですが、デリゲートを使用してそれを実行する方法は次のとおりです。

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);
0
Hallgrim

チェックアウト 暗黙の変換 。別のオプションは 明示的な変換 ですが、オブジェクトタイプをキャストする必要があります。

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
Matthew Whited