C # 4.0: Tôi có thể sử dụng TimeSpan làm tham số tùy chọn với giá trị mặc định không?


125

Cả hai đều tạo ra một lỗi cho biết chúng phải là hằng số thời gian biên dịch:

void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))

Trước hết, ai đó có thể giải thích tại sao những giá trị này không thể được xác định tại thời điểm biên dịch không? Và có cách nào để chỉ định giá trị mặc định cho đối tượng TimeSpan tùy chọn không?


11
Không liên quan đến những gì bạn yêu cầu, nhưng lưu ý rằng điều 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.
Jeppe Stig Nielsen

Câu trả lời:


172

Bạn có thể làm việc xung quanh điều này rất dễ dàng bằng cách thay đổi chữ ký của bạn.

void Foo(TimeSpan? span = null) {

   if (span == null) { span = TimeSpan.FromSeconds(2); }

   ...

}

Tôi nên giải thích - lý do các biểu thức trong ví dụ của bạn không phải là hằng số thời gian biên dịch là vì tại thời gian biên dịch, trình biên dịch không thể thực thi TimeSpan.FromSeconds (2.0) và gắn các byte kết quả vào mã được biên dịch của bạn.

Ví dụ, xem xét nếu bạn đã cố gắng sử dụng DateTime. Thay vào đó. Giá trị của DateTime. Bây giờ thay đổi mỗi khi nó được thực thi. Hoặc giả sử rằng TimeSpan.FromSeconds đã tính đến trọng lực. Đó là một ví dụ vô lý nhưng các quy tắc của hằng số thời gian biên dịch không tạo ra các trường hợp đặc biệt chỉ vì chúng ta biết rằng TimeSpan.FromSeconds là xác định.


15
Bây giờ ghi lại giá trị mặc định <param>, vì nó không hiển thị trong chữ ký.
Đại tá hoảng loạn

3
Tôi không thể làm điều này, tôi đang sử dụng giá trị đặc biệt null cho thứ khác.
Đại tá Panic

4
@MattHickford - Sau đó, bạn sẽ phải cung cấp một phương thức quá tải hoặc lấy mili giây làm tham số.
Josh

19
Cũng có thể sử dụng span = span ?? TimeSpan.FromSeconds(2.0);với kiểu nullable, trong thân phương thức. Hoặc var realSpan = span ?? TimeSpan.FromSeconds(2.0);để có được một biến cục bộ không thể rỗng.
Jeppe Stig Nielsen

5
Điều tôi không thích ở đây là nó ngụ ý cho người dùng chức năng mà chức năng này "hoạt động" với một khoảng rỗng. Nhưng điều đó không đúng! Null không phải là một giá trị hợp lệ cho khoảng thời gian liên quan đến logic thực tế của hàm. Tôi ước có một cách tốt hơn mà dường như không có mùi mã ...
JoeCool

31

Di sản VB6 của tôi khiến tôi không yên tâm với ý tưởng coi "giá trị null" và "giá trị còn thiếu" là tương đương. Trong hầu hết các trường hợp, điều đó có thể tốt, nhưng bạn có thể có tác dụng phụ ngoài ý muốn hoặc bạn có thể nuốt phải một điều kiện đặc biệt (ví dụ: nếu nguồn của spanmột thuộc tính hoặc biến không nên là null, nhưng là).

Do đó tôi sẽ quá tải phương thức:

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}

1
+1 cho kỹ thuật tuyệt vời đó. Các tham số mặc định chỉ nên được sử dụng với các kiểu const. Khác, nó không đáng tin cậy.
Lazlo

2
Đây là cách tiếp cận 'thời gian được tôn vinh' mà các giá trị mặc định đã thay thế và trong tình huống này tôi nghĩ rằng đây là câu trả lời ít xấu nhất;) Bản thân nó không nhất thiết phải hoạt động tốt cho các giao diện, bởi vì bạn thực sự muốn giá trị mặc định trong một nơi. Trong trường hợp này, tôi đã thấy các phương thức mở rộng là một công cụ hữu ích: giao diện có một phương thức với tất cả các tham số, sau đó một loạt các phương thức mở rộng được khai báo trong một lớp tĩnh cùng với giao diện thực hiện mặc định trong các tình trạng quá tải khác nhau.
OlduwanSteve

23

Điều này hoạt động tốt:

void Foo(TimeSpan span = default(TimeSpan))


4
Chào mừng bạn đến với Stack Overflow. Câu trả lời của bạn dường như là bạn có thể cung cấp một giá trị tham số mặc định, miễn là đó là một giá trị rất cụ thể mà trình biên dịch cho phép. Tôi đã hiểu đúng chưa? (Bạn có thể chỉnh sửa câu trả lời của mình để làm rõ.) Đây sẽ là câu trả lời tốt hơn nếu nó chỉ ra cách tận dụng những gì trình biên dịch cho phép để đạt được những gì câu hỏi ban đầu, đó là có các TimeSpan giá trị khác tùy ý , như được đưa ra bởi new TimeSpan(2000).
Rob Kennedy

2
Một thay thế sử dụng một số giá trị mặc định cụ thể sẽ là sử dụng một thời gian chỉ đọc tĩnh riêng tư TimeSpan defaultTimespan = Timespan.FromSeconds (2) kết hợp với hàm tạo và hàm tạo mặc định mất một khoảng thời gian. công khai Foo (): này (defaultTimespan) và công khai Foo (Timespan ts)
johan mårtensson

15

Tập hợp các giá trị có thể được sử dụng làm giá trị mặc định giống như có thể được sử dụng cho đối số thuộc tính. Lý do là các giá trị mặc định được mã hóa thành siêu dữ liệu bên trong DefaultParameterValueAttribute.

Về lý do tại sao nó không thể được xác định tại thời điểm biên dịch. Tập hợp các giá trị và biểu thức trên các giá trị như vậy được phép tại thời điểm biên dịch được liệt kê trong thông số ngôn ngữ C # chính thức :

C # 6.0 - Các loại tham số thuộc tính :

Các loại tham số vị trí và được đặt tên cho một lớp thuộc tính được giới hạn trong các loại tham số thuộc tính , đó là:

  • Một trong các loại sau: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • Các loại object.
  • Các loại System.Type.
  • Một loại enum.
    (miễn là nó có khả năng truy cập công cộng và các loại mà nó được lồng vào nhau (nếu có) cũng có khả năng truy cập công cộng)
  • Mảng một chiều của các loại trên.

Loại TimeSpankhông phù hợp với bất kỳ danh sách nào trong số này và do đó không thể được sử dụng như một hằng số.


2
Chọn nit nhẹ: Gọi một phương thức tĩnh không phù hợp với bất kỳ danh sách nào. TimeSpancó thể phù hợp với cái cuối cùng trong danh sách default(TimeSpan)này là hợp lệ.
CodeInChaos

12
void Foo(TimeSpan span = default(TimeSpan))
{
    if (span == default(TimeSpan)) 
        span = TimeSpan.FromSeconds(2); 
}

cung cấp default(TimeSpan)không phải là một giá trị hợp lệ cho chức năng.

Hoặc là

//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
    if (span == new TimeSpan()) 
        span = TimeSpan.FromSeconds(2); 
}

cung cấp new TimeSpan()không phải là một giá trị hợp lệ.

Hoặc là

void Foo(TimeSpan? span = null)
{
    if (span == null) 
        span = TimeSpan.FromSeconds(2); 
}

Điều này nên được xem xét tốt hơn cơ hội nullgiá trị là một giá trị hợp lệ cho hàm là rất hiếm.


4

TimeSpanlà trường hợp đặc biệt cho DefaultValueAttributevà được chỉ định bằng cách sử dụng bất kỳ chuỗi nào có thể được phân tích cú pháp thông qua TimeSpan.Parsephương thức.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }

3

Đề xuất của tôi:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

BTW TimeSpan.FromSeconds(2.0)không bằng nhau new TimeSpan(2000)- hàm tạo có dấu tích.


2

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 nullcho 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 TimeSpannull 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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.