Other answers 已经很好地解释了为什么可选参数不能是动态表达式。但是,回顾一下,默认参数的行为类似于编译时常量。这意味着编译器必须能够评估它们并提出答案。有些人希望 C# 在遇到常量声明时添加对编译器评估动态表达式的支持——这种特性与标记方法“纯”有关,但现在这不是现实,而且可能永远不会。 /p>
对这种方法使用 C# 默认参数的另一种方法是使用 XmlReaderSettings 示例的模式。在此模式中,定义一个具有无参数构造函数和可公开写入属性的类。然后用这种类型的对象替换方法中的所有选项和默认值。甚至通过为其指定默认值null 来使该对象成为可选对象。例如:
public class FooSettings
{
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);
// I imagine that if you had a heavyweight default
// thing you’d want to avoid instantiating it right away
// because the caller might override that parameter. So, be
// lazy! (Or just directly store a factory lambda with Func<IThing>).
Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
public IThing Thing
{
get { return thing.Value; }
set { thing = new Lazy<IThing>(() => value); }
}
// Another cool thing about this pattern is that you can
// add additional optional parameters in the future without
// even breaking ABI.
//bool FutureThing { get; set; } = true;
// You can even run very complicated code to populate properties
// if you cannot use a property initialization expression.
//public FooSettings() { }
}
public class Bar
{
public void Foo(FooSettings settings = null)
{
// Allow the caller to use *all* the defaults easily.
settings = settings ?? new FooSettings();
Console.WriteLine(settings.Span);
}
}
要调用,请使用一种奇怪的语法在一个表达式中实例化和分配属性:
bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02
缺点
这是解决此问题的真正重量级方法。如果您正在编写一个快速而肮脏的内部接口并且making the TimeSpan nullable and treating null like your desired default value 可以正常工作,那么请改为这样做。
此外,如果您有大量参数或在紧密循环中调用方法,这将产生类实例化的开销。当然,如果在紧密循环中调用这样的方法,那么重用FooSettings 对象的实例可能很自然,甚至非常容易。
好处
正如我在示例中的评论中提到的,我认为这种模式非常适合公共 API。向类添加新属性是一项非破坏性的 ABI 更改,因此您可以添加新的可选参数而无需使用此模式更改方法的签名 — 为最近编译的代码提供更多选项,同时继续支持旧的编译代码而无需额外工作.
此外,由于 C# 的内置默认方法参数被视为编译时常量并被烘焙到调用站点中,因此只有在重新编译后代码才会使用默认参数。通过实例化设置对象,调用者在调用您的方法时会动态加载默认值。这意味着您可以通过更改设置类来更新默认值。因此,此模式允许您更改默认值,而无需重新编译调用者以查看新值(如果需要)。