3

プログラミングに盲点はありますか?

つまり、慣れることができない一般的なテクニックや言語機能があるということです。まあ、私は1つ(またはおそらく複数)持っており、私の使用法はdelegate. ハンズアップ!デリゲートに満足していない人は他にいますか? 正直に言ってください!

では、デリゲートとは何ですか?

大学でのコースで C を紹介されたので、関数ポインターについて知っています。メソッドを引数として渡したい場合は、関数ポインタが便利です。したがって、私の考えでは、デリゲートは関数ポインターのようなものです。ユーレカ!わかった。していません!

具体的なシナリオ?

正規表現に一致する行をテキスト ファイルから削除したいと考えています。行のコレクションがあると仮定すると、その目的に完全に適していると思われる List<T>方法があります。リスト要素を削除するか残すかを決定するための引数として、評価メソッドが必要です。関数ポインタです。RemoveAllRemoveAll

ここにコードはありますか?

public static int RemoveLinesFromFile(string path, string pattern)
{
  List<string> lines = new List<string>(File.ReadAllLines(path));
  int result = lines.RemoveAll(DoesLineMatch);
  File.WriteAllLines(path, lines.ToArray());
  return result;
}

DoesLineMatch行がパターンに一致するかどうかを評価する関数を探しています。

問題が見えますか?

RemoveAllPredicate<string> match引数としてデリゲートが必要です。次のようにコーディングします。

private static bool DoesLineMatch(string line, string pattern)
{
  return Regex.IsMatch(line, pattern);
}

しかし、「'bool DoesLineMatch(string)' シグネチャを持つメソッドが必要です」というエラーが表示されます。ここで何が欠けていますか?

それはまったく機能しますか?

これが私が最終的にそれを機能させた方法です:

public static int RemoveLinesFromFile(string path, string pattern)
{
  List<string> lines = new List<string>(File.ReadAllLines(path));
  int result = lines.RemoveAll(delegate(string line)
    {
      return Regex.IsMatch(line, pattern);
    });
  File.WriteAllLines(path, lines.ToArray());
  return result;
}

それが機能することはうれしいですが、私はそれを理解していません。

そして、質問は何ですか?

それを機能させるために私がしたことは、単にメソッドをインライン化することです。私がインライン化を理解している限り、それはある種の一度使用して破棄するコードにすぎません。変数またはメソッドを 1 回だけ使用する場合はインライン化できますが、インライン化は常に明示的に宣言することと同じです。

メソッドを明示的に宣言する方法はありますか? どうすればいいですか?

PS .: 私の質問がやや長いことをお許しください。

PPS .: このデリゲートを取得したらすぐに、2.0 から 3.0 に飛躍し、ラムダを学習します。

PPPS .:の効率に関するJon のヒントRegex.IsMatch(string, string)に従って、コードを変更しました。

  int result = lines.RemoveAll(delegate(string line)
    {
      Regex regex = new Regex(pattern);
      return regex.IsMatch(line);
    });

それは効率の問題に関してはあまり役に立ちません。そこで、ReSharperの提案に従い、正規表現のインスタンス化を外側のスコープに移動しました。

  Regex regex = new Regex(pattern);
  int result = lines.RemoveAll(delegate(string line)
    {
      return regex.IsMatch(line);
    });

ReSharper は、これをメソッド グループに置き換えるように促しました。

  Regex regex = new Regex(pattern);
  int result = lines.RemoveAll(regex.IsMatch);

そして、それはここで提案された答えと非常に似ています。私が求めたものではありませんが、ReSharper (そしてもちろんスタック オーバーフロー) が学習にどのように役立つかに再び驚いています。

4

7 に答える 7

2

基本的に、匿名のデリゲートにより、コンパイラは次のことを実行します。フィールド'pattern'を持つ発音できない名前のクラスと、デリゲートで記述したのと同様のメソッドを生成します。生成されたクラスは次のようになります。

class Matcher {
    public string Pattern;
    bool IsMatch(string value){
       return Regex.IsMatch(Pattern, value);
    }
}

ご覧のとおり、このクラスは2つの引数関数を1つの引数を持つ関数に変換します。

あなたのコードは次のようなものに変換されます

public static int RemoveLinesFromFile(string path, string pattern)
{
  List<string> lines = new List<string>(File.ReadAllLines(path));
  Matcher matcher = new Matcher(pattern);
  int result = lines.RemoveAll(matcher.IsMatch);
  File.WriteAllLines(path, lines.ToArray());
  return result;
}

ご覧のとおり、ランタイムはスコープから変数を取得し、それを関数にバインドします。これで、追加の変数を囲む必要な署名を持つ関数ができました。そのため、CSの観点からデリゲートはクロージャと呼ばれます。もちろん、言及されているものはすべて手動で作成できます。これは、より簡単な方法です。

お役に立てれば。

于 2009-10-02T10:52:54.263 に答える
2

ここで他の回答のいくつかを拡張するために、C# の一般的なカリー化関数を次に示します。

public static class DelegateUtils
{
    public static Predicate<T> ToPredicate<T>(this Func<T, Boolean> func)
    {
        return value => func(value);
    }

    public static Func<TResult> Curry<T1, TResult>(
        this Func<T1, TResult> func, T1 firstValue)
    {
        return () => func(firstValue);
    }

    public static Func<T2, TResult> Curry<T1, T2, TResult>(
        this Func<T1, T2, TResult> func, T1 firstValue)
    {
        return p2 => func(firstValue, p2);
    }

    public static Func<T2, T3, TResult> Curry<T1, T2, T3, TResult>(
        this Func<T1, T2, T3, TResult> func, T1 firstValue)
    {
        return (p2, p3) => func(firstValue, p2, p3);
    }

    // if you need more, follow the examples
}

あなたの例では、次のように、一致させたいパラメーターが最初になるように、引数の順序を一致する関数に切り替えます。

private static bool DoesLineMatch(string pattern, string line)
{
    return Regex.IsMatch(line, pattern);
}

次に、カリー化を使用して最初のパラメーターを修正し、次のように述語に変換できるデリゲートを取得します。

Func<String, String, Boolean> func = DoesLineMatch;
Func<String, Boolean> predicateCandidate = func.Curry("yourPattern");
Predicate<String> predicate = predicateCandidate.ToPredicate();
lines.RemoveAll(predicate);

もちろん、すべてをインライン化できます。

lines.RemoveAll(new Func<String, String, Boolean>(DoesLineMatch)
    .Curry("yourPattern")
    .ToPredicate());
于 2009-10-02T12:09:12.160 に答える
1

ここで悩まされているのは、C プログラマーが通常、異なる引数を持つ関数を異なる型付けと見なさないという現象です。単一の文字列引数を持つ関数は、たとえば Algol 68 で発生するように、コンパイル時に型エラーを生成する必要があります。

これは C 言語のせいでもあります。実際、C 言語は、引数と戻り値の型によって関数ポインターを正しく型付けできます。しかし、これらの型の表記法は非常に厄介です。C コンパイラは常にこれを必要とするわけではありません。必要な場合、プログラマーはすべてのポインターを (void *) にキャストすることで回避する傾向があります。

第一言語として C を学ぶことは、いくつかの悪い習慣を教えてくれます。

于 2009-10-19T17:30:16.657 に答える
1

C# 2.0 では、パターン変数をキャプチャするために使用できる匿名デリゲートを作成できます。

        int result = lines.RemoveAll( delegate (string s) {return DoesLineMatch(s, pattern);});
于 2009-10-02T10:48:45.660 に答える