Các câu trả lời khác đã đưa ra những giải thích tuyệt vời về lý do tại sao một tham số tùy chọn không thể là biểu thức động. Nhưng, để kể lại, các tham số mặc định hoạt động giống như các hằng số thời gian biên dịch. Điều đó có nghĩa là trình biên dịch phải có khả năng đánh giá chúng và đưa ra câu trả lời. Có một số người muốn C # thêm hỗ trợ cho trình biên dịch đánh giá các biểu thức động khi gặp phải các khai báo liên tục, loại tính năng này sẽ liên quan đến các phương thức đánh dấu, thuần túy, nhưng đó không phải là thực tế ngay bây giờ và có thể không bao giờ.
Một cách khác để sử dụng tham số mặc định C # cho phương thức như vậy là sử dụng mẫu được minh họa bằng XmlReaderSettings
. Trong mẫu này, định nghĩa một lớp với hàm tạo không tham số và các thuộc tính có thể ghi công khai. Sau đó thay thế tất cả các tùy chọn bằng mặc định trong phương thức của bạn bằng một đối tượng thuộc loại này. Thậm chí làm cho đối tượng này là tùy chọn bằng cách chỉ định mặc định null
cho nó. Ví dụ:
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);
}
}
Để gọi, sử dụng một cú pháp kỳ lạ đó để khởi tạo và gán các thuộc tính tất cả trong một biểu thức:
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
Nhược điểm
Đây là một cách tiếp cận thực sự nặng nề để giải quyết vấn đề này. Nếu bạn đang viết một giao diện nội bộ nhanh và bẩn và làm cho TimeSpan
null và xử lý null như giá trị mặc định mong muốn của bạn sẽ hoạt động tốt, thay vào đó hãy làm điều đó.
Ngoài ra, nếu bạn có một số lượng lớn các tham số hoặc đang gọi phương thức trong một vòng lặp chặt chẽ, điều này sẽ có chi phí hoạt động tức thời của lớp. Tất nhiên, nếu gọi một phương thức như vậy trong một vòng lặp chặt chẽ, nó có thể là tự nhiên và thậm chí rất dễ dàng để sử dụng lại một thể hiện của FooSettings
đối tượng.
Những lợi ích
Như tôi đã đề cập trong bình luận trong ví dụ, tôi nghĩ mẫu này rất phù hợp với các API công khai. Thêm các thuộc tính mới vào một lớp là một thay đổi ABI không phá vỡ, vì vậy bạn có thể thêm các tham số tùy chọn mới mà không thay đổi chữ ký của phương thức của mình bằng cách sử dụng mẫu này. Cung cấp thêm mã được biên dịch gần đây trong khi tiếp tục hỗ trợ mã được biên dịch cũ mà không cần làm thêm .
Ngoài ra, bởi vì các tham số phương thức mặc định được xây dựng của C # được coi là hằng số compXLime và được đưa vào callite, các tham số mặc định sẽ chỉ được sử dụng bởi mã khi được biên dịch lại. Bằng cách khởi tạo một đối tượng cài đặt, người gọi sẽ tự động tải các giá trị mặc định khi gọi phương thức của bạn. Điều này có nghĩa là bạn có thể cập nhật mặc định bằng cách chỉ thay đổi lớp cài đặt của bạn. Do đó, mẫu này cho phép bạn thay đổi các giá trị mặc định mà không phải biên dịch lại người gọi để xem các giá trị mới, nếu điều đó là mong muốn.
new TimeSpan(2000)
đó không có nghĩa là 2000 mili giây, nó có nghĩa là 2000 "tích tắc" là 0,2 mili giây, hoặc một phần hai của hai giây.