我的意思是有一种你不能真正习惯的常用技术或语言功能。
好吧,我有一个(或者可能不止一个),我的使用是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)如何帮助学习。
答案 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的模式变量使用闭包。