为什么不内联一个等效于明确声明它的方法?

时间:2009-10-02 10:30:08

标签: c# delegates c#-2.0 inline

你对编程有盲点吗?

我的意思是有一种你不能真正习惯的常用技术或语言功能。  好吧,我有一个(或者可能不止一个),我的使用是delegate。  举手!还有谁对代表感到不舒服?说实话!

那么代表是什么?

由于我在大学的课程向我介绍了C,我知道函数指针。  如果要将方法作为参数传递,函数指针很方便。  所以在我看来,委托就像一个函数指针。找到了!我知道了。我没有!

具体情况?

我想删除与regular expression匹配的文本文件中的任何行。  假设我有一组行,List<T>有方法RemoveAll,它似乎非常适合这个目的。  RemoveAll期望将评估方法作为决定是否删除或保留列表元素的参数。  它就是:函数指针!

这里有任何代码吗?

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来评估一条线是否与一个模式匹配。

你看到了问题吗?

RemoveAll期望委托Predicate<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;
}

我很高兴它有效,但我不明白。

问题是什么?

我为使其工作所做的只是简单介绍该方法。  据我所知,它只是某种使用 - 一次破坏 - 代码。  如果您只使用变量或方法一次内联它,但内联总是等同于明确声明它。

有没有办法明确声明方法?我该怎么做?

PS:赦免我,我的问题有些冗长。

PPS:一旦我得到这个代表的话,我就会从2.0跳到3.0并学习lambdas。

PPPS:Regex.IsMatch(string, string)效率 int result = lines.RemoveAll(delegate(string line) { Regex regex = new Regex(pattern); return regex.IsMatch(line); }); 我修改了我的代码:

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

这对效率问题没有多大帮助。所以我按照Jon's hint的提议,将Regex实例化移到了外部范围:

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

现在ReSharper敦促我用方法组替换它:

{{1}}

这与此处提出的答案非常相似。不是我要求的,但我又惊讶于ReSharper(当然还有Stack Overflow)如何帮助学习。

6 个答案:

答案 0 :(得分:8)

您正在尝试使用签名为:

的方法
bool DoesLineMatch(string line, string pattern)

代表签名:

bool Predicate(string value)

从哪里获得第二个字符串值(模式)?

使用显式声明的方法执行此操作的唯一方法是:

public sealed class RegexHolder
{
    private readonly string pattern;

    public RegexHolder(string pattern)
    {
        this.pattern = pattern;
    }

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

然后:

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

这与编译器使用匿名方法为您做的很接近 - 它将创建一个嵌套类来保存捕获的变量(在这种情况下为pattern)。

(请注意,我已经避免讨论调用Regex.Match(string, string)的效率而不是创建Regex的单个实例......这是另一回事。)

答案 1 :(得分:2)

基本上,您的匿名委托会导致编译器执行以下操作:生成一个具有不可发音名称的类,该类具有字段'pattern'和类似于您在委托中编写的方法。 生成的类看起来像这样:

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

你看,这个类将两个参数函数转换为一个带有一个参数的函数。

您的代码转换为

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的角度来看代理被称为闭包的原因。 当然,所提到的一切都可以手动完成,这只是一种更简单的方法。

希望这有帮助。

答案 2 :(得分:2)

为了扩展其他一些答案,这里是C#的通用currying函数:

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

然后你将使用currying来修复第一个参数,并获得一个委托,然后你可以转换为谓词,如下所示:

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());

答案 3 :(得分:1)

在C#2.0中,您可以创建一个匿名委托,您可以使用它来捕获您的模式变量:

        int result = lines.RemoveAll( delegate (string s) {return DoesLineMatch(s, pattern);});

答案 4 :(得分:0)

Wot Jon说。

此外,在C#3中,您可以选择使用lambda,假设您仍希望将pattern传递给您的方法:

int result = lines.RemoveAll(l => DoesLineMatch(l, pattern));

答案 5 :(得分:0)

您可以这样声明:

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

其中pattern是您类中的私有变量。但这有点难看,这就是为什么你可以声明内联删除并为你在RemoveLinesFromFile方法中声明为localy的模式变量使用闭包。