为什么内联方法不等于显式声明它?

发布于 2024-08-06 15:54:16 字数 2768 浏览 6 评论 0原文

你在编程中是否有盲点?

我的意思是,是否存在您无法真正习惯的通用技术或语言功能。 好吧,我有一个(或者可能不止一个),我的就是使用 delegate。 举起手来!还有谁对代表感到不舒服?说实话!

那么什么是代表呢?

自从大学课程向我介绍了 C 语言以来,我就了解了函数指针。 如果您想将方法作为参数传递,函数指针会很方便。 所以在我看来,委托就像一个函数指针。尤里卡!我得到了它。我没有!

具体场景?

我想从文本文件中删除与正则表达式匹配的任何行。 假设我有一组行, List 的方法 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; match 作为参数。 我会像这样编码:

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

但随后我收到错误“期望具有'boolDoesLineMatch(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.:一旦我得到了这个 delegate 的东西,我就会从 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);
    });

这对于效率问题没有太大帮助。因此,我遵循 ReSharper 的提议,并将 Regex 实例化移至外部范围:

  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(当然还有 Stack Overflow)如何帮助学习感到惊讶。

Do you have a blind spot in programming?

I mean is there a common technique or language feature that you can't really get used to.
Well, I have one (or probably more than one) and mine is usage of delegate.
Hands up! Who else doesn't feel comfortable with delegates? Be honest!

So what's a delegate?

Since my courses at university introduced me to C, I know about function pointers.
Function pointers are handy if you want to pass methods as arguments.
So in my mind a delegate is something like a function pointer. Eureka! I got it. I have not!

A concrete scenario?

I would like to remove any line from a text file that matches a regular expression.
Assuming I have a collection of lines, List<T> has method RemoveAll which seems to be perfectly suitable for that purpose.
RemoveAll expects an evaluation method as argument for deciding on whether to remove or leave a list element.
And there it is: The function pointer!

Any code here?

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

So I'm looking for a function DoesLineMatch which evaluates if a line matches a pattern.

Do you see the problem?

RemoveAll expects a delegate Predicate<string> match as argument.
I would have coded it like this:

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

But then I'm getting an error "Expected a method with 'bool DoesLineMatch(string)' signature".
What am I missing here?

Does it work at all?

This is how I finally got it working:

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

I'm happy that it works but I don't understand it.

And what is the question?

What I did to get it working is simply inlining the method.
As far as I understand inlining, it is just some kind of use-once-and-destroy-code.
If you use a variable or method only once you may inline it, but inlining is always equivalent to declaring it explicitly.

Is there a way to declare the method explicitly? How would I do it?

PS.: Pardon me that my question is somewhat lengthy.

PPS.: As soon as I get this delegate thing I will make the leap from 2.0 to 3.0 and learn lambdas.

PPPS.: Following Jon's hint on efficiency of Regex.IsMatch(string, string) I modified my code:

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

That isn't of much help regarding efficiency matters. So I followed ReSharper's proposal and moved the Regex instantiation to the outer scope:

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

Now ReSharper urged me to replace this with a method group:

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

And that is quite similar to the answers proposed here. Not what I asked for, but again I'm amazed how ReSharper (and Stack Overflow of course) helps learning.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

别挽留 2024-08-13 15:54:16

您正在尝试使用具有以下签名的方法:

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

这与编译器使用匿名方法为您所做的操作很接近 - 它将创建一个嵌套类来保存捕获的变量 em> (在本例中是pattern)。

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

You're trying to use a method with a signature of:

bool DoesLineMatch(string line, string pattern)

for a delegate with signature:

bool Predicate(string value)

Where would it get the second string value (the pattern) from?

The only way to do this with an explicitly declared method would be something like this:

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

Then:

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

That's close to what the compiler's doing for you with the anonymous method - it will have created a nested class to hold the captured variable (pattern in this case).

(Note that I've avoided any discussion of the efficiency of calling Regex.Match(string, string) rather than creating a single instance of the Regex... that's a different matter.)

剪不断理还乱 2024-08-13 15:54:16

基本上,您的匿名委托会导致编译器执行以下操作:生成一个名称难以发音的类,该类具有“模式”字段和类似于您在委托中编写的方法。
生成的类如下所示:

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

希望这有帮助。

Basically, your anonymous delegate causes compiler to do following: generate an class with unpronounceable name having a field 'pattern' and a method similar to written by you in a delegate.
Generated class looks like this:

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

You see, this class converts two argument function to a function with one argument.

Your code is converted to something like

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

You see, runtime takes a variable from scope and binds it with function. Now you have a function with required signature that encloses additional variable. That's why delegates are called closures from CS point of view.
Of course, everything mentioned can be made manually, this is just a more simple way of doing it.

Hope this helps.

难以启齿的温柔 2024-08-13 15:54:16

为了扩展这里的一些其他答案,这里有一个 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());

To expand on some of the other answers here, here's a generic currying function for 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
}

In your example, you would switch the order of the arguments to your matching function, so that the parameter you want to match against is the first, like this:

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

Then you would use currying to fix the first parameter, and obtain a delegate that you could then convert to a predicate, like this:

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

of course, you can inline it all:

lines.RemoveAll(new Func<String, String, Boolean>(DoesLineMatch)
    .Curry("yourPattern")
    .ToPredicate());
贪了杯 2024-08-13 15:54:16

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

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

In C# 2.0 you can create an anonymous delegate, which you can use to capture your pattern variable:

        int result = lines.RemoveAll( delegate (string s) {return DoesLineMatch(s, pattern);});
傲性难收 2024-08-13 15:54:16

这里你所遇到的现象是,C 程序员通常不会将具有不同参数的函数视为不同类型 - 他们不会想到将指针传递给具有两个字符串参数的函数,其中一个指针指向一个预计具有单个字符串参数的函数应该在编译时生成类型错误,就像在 Algol 68 中发生的那样。C

语言对此负有部分责任:事实上,它可以通过参数和返回类型正确键入函数指针。但这些类型的表示法确实很尴尬,C 编译器并不总是需要这个,当他们这样做时,程序员往往会通过将所有指针强制转换为 (void *) 来绕过它。

将 C 作为第一语言学习确实会教会你一些坏习惯。

What you've been bitten by here is the phenomenon that C programmers don't usually consider functions with different arguments as differently typed - it doesn't occur to them that passing a pointer to a function with two string arguments where a pointer to a function with a single string argument is expected should generate a type error at compile time, as would happen in e.g. Algol 68.

The C language is only partially to blame for this: it can in fact properly type function pointers by argument and return types. But the notation for these types is really awkward, C compilers don't always require this, and when they do programmers tend to get around it by casting all pointers to (void *) anyway.

Learning C as a first language does teach you some bad habits.

百善笑为先 2024-08-13 15:54:16

乔恩说。

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

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

Wot Jon Says.

Furthermore, in C# 3 you might choose to use a lambda, assuming you still want to pass pattern to your method:

int result = lines.RemoveAll(l => DoesLineMatch(l, pattern));
孤城病女 2024-08-13 15:54:16

您可以这样声明它:

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

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

You could declare it like this:

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

Where pattern is a private variable in you class. But that's a bit ugly, that why you can declare the deleage inline and use a closure for the pattern variable that is declared localy in your RemoveLinesFromFile method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文