void in C # generics?


94

Tôi có một phương pháp chung nhận yêu cầu và cung cấp phản hồi.

public Tres DoSomething<Tres, Treq>(Tres response, Treq request)
{/*stuff*/}

Nhưng không phải lúc nào tôi cũng muốn có phản hồi cho yêu cầu của mình và không phải lúc nào tôi cũng muốn cung cấp dữ liệu yêu cầu để nhận được phản hồi. Tôi cũng không muốn phải sao chép và dán toàn bộ các phương thức của chúng để thực hiện các thay đổi nhỏ. Điều tôi muốn là có thể làm được điều này:

public Tre DoSomething<Tres>(Tres response)
{
    return DoSomething<Tres, void>(response, null);
}

Điều này có khả thi theo một cách nào đó không? Có vẻ như việc sử dụng cụ thể void không hoạt động, nhưng tôi hy vọng sẽ tìm thấy thứ gì đó tương tự.


1
Tại sao không chỉ sử dụng System.Object và kiểm tra rỗng trong DoSomething (phản hồi Tres, yêu cầu Treq)?
James

Lưu ý rằng bạn cần sử dụng giá trị trả về. Bạn có thể gọi các hàm như các thủ tục. DoSomething(x);thay vìy = DoSomething(x);
Olivier Jacot-Descombes

1
Tôi nghĩ bạn muốn nói, "Lưu ý rằng bạn không cần sử dụng giá trị trả về." @ OlivierJacot-Descombes
zanedp

Câu trả lời:


95

Bạn không thể sử dụng void, nhưng bạn có thể sử dụng object: hơi bất tiện vì các hàm mong muốn của bạn voidcần phải trả về null, nhưng nếu nó thống nhất mã của bạn, thì đó sẽ là một cái giá nhỏ phải trả.

Việc không thể sử dụng voidlàm kiểu trả về này ít nhất cũng chịu trách nhiệm một phần cho sự phân chia giữa họ Func<...>Action<...>họ của các đại biểu chung: nếu có thể trả về void, tất cả Action<X,Y,Z>sẽ trở nên đơn giản Func<X,Y,Z,void>. Thật không may, điều này là không thể.


45
(Joke) Và anh ta vẫn có thể quay trở lại voidtừ những phương pháp sẽ vô hiệu đó, với return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Tuy nhiên, nó sẽ là một khoảng trống đóng hộp.
Jeppe Stig Nielsen

Vì C # hỗ trợ nhiều tính năng lập trình chức năng hơn, bạn có thể xem qua Đơn vị đại diện voidtrong FP. Và có những lý do chính đáng để sử dụng nó. Trong F #, vẫn là .NET, chúng tôi đã tích unithợp sẵn.
joe

87

Không, tiếc là không. Nếu voidlà một kiểu "thực" (như unittrong F # chẳng hạn) thì cuộc sống sẽ đơn giản hơn rất nhiều theo nhiều cách. Đặc biệt, chúng tôi sẽ không cần cả gia đình Func<T>Action<T>gia đình - sẽ chỉ có Func<void>thay vì Action, Func<T, void>thay vì Action<T>v.v.

Nó cũng sẽ làm cho việc không đồng bộ trở nên đơn giản hơn - sẽ không cần loại không chung chung nào Taskcả - chúng tôi chỉ cần có Task<void>.

Thật không may, đó không phải là cách hệ thống loại C # hoặc .NET hoạt động ...


4
Unfortunately, that's not the way the C# or .NET type systems work...Bạn đang khiến tôi hy vọng rằng cuối cùng mọi thứ có thể sẽ diễn ra như vậy. Điểm cuối cùng của bạn có nghĩa là chúng ta không bao giờ có khả năng mọi thứ diễn ra theo cách đó?
Dave Cousineau

2
@Sahuagin: Tôi nghi ngờ là không - đó sẽ là một thay đổi khá lớn vào thời điểm này.
Jon Skeet

1
@stannius: Không, tôi nghĩ đó là một điều bất tiện hơn là dẫn đến mã không chính xác.
Jon Skeet

2
Có lợi thế khi có kiểu tham chiếu Đơn vị thay vì kiểu giá trị trống để biểu thị "void"? Loại giá trị trống có vẻ phù hợp hơn với tôi, nó không có giá trị và không chiếm không gian. Tôi tự hỏi tại sao void không được thực hiện như thế này. Việc đưa hay không bật nó từ ngăn xếp sẽ không có gì khác biệt (nói mã gốc, trong IL nó có thể khác).
Ondrej Petrzilka

1
@Ondrej: Tôi đã thử sử dụng một cấu trúc trống cho mọi thứ trước đây, và kết quả là nó không trống trong CLR ... Tất nhiên, nó có thể được đặt chữ đặc biệt. Ngoài ra, tôi không biết tôi sẽ đề xuất cái nào; Tôi đã không nghĩ về nó nhiều.
Jon Skeet

27

Dưới đây là những gì bạn có thể làm. Như @JohnSkeet đã nói rằng không có loại đơn vị nào trong C #, vì vậy hãy tự tạo nó!

public sealed class ThankYou {
   private ThankYou() { }
   private readonly static ThankYou bye = new ThankYou();
   public static ThankYou Bye { get { return bye; } }
}

Bây giờ bạn luôn có thể sử dụng Func<..., ThankYou>thay vìAction<...>

public ThankYou MethodWithNoResult() {
   /* do things */
   return ThankYou.Bye;
}

Hoặc sử dụng thứ gì đó đã được nhóm Rx tạo ra: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx


System.Reactive.Unit là một gợi ý hay. Tính đến tháng 11 năm 2016, nếu bạn đang quan tâm trong việc như một mảnh nhỏ của khuôn khổ phản ứng càng tốt, bởi vì bạn không sử dụng bất cứ điều gì khác hơn so với lớp đơn vị, sử dụng quản lý gói NuGetInstall-Package System.Reactive.Core
DannyMeister

6
Đổi tên ThankYou thành "KThx" và đó là người chiến thắng. ^ _ ^Kthx.Bye;
LexH

Chỉ để kiểm tra tôi không thiếu thứ gì đó .. Bye getter không thêm bất cứ điều gì quan trọng qua truy cập trực tiếp ở đây phải không?
Andrew

1
@ Andrew bạn không cần phải tạm biệt getter, trừ khi bạn muốn làm theo tinh thần của C # mã hóa, trong đó nói rằng bạn không nên tiếp xúc với lĩnh vực trần
Trident D'Gao

16

Bạn chỉ có thể sử dụng Objectnhư những người khác đã đề xuất. Hoặc Int32mà tôi đã thấy một số sử dụng. Sử dụng Int32giới thiệu một số "giả" (sử dụng 0), nhưng ít nhất bạn không thể đặt bất kỳ đối tượng lớn và kỳ lạ nào vào một Int32tham chiếu (các cấu trúc được niêm phong).

Bạn cũng có thể viết loại "void" của riêng bạn:

public sealed class MyVoid
{
  MyVoid()
  {
    throw new InvalidOperationException("Don't instantiate MyVoid.");
  }
}

MyVoidcác tham chiếu được phép (nó không phải là một lớp tĩnh) nhưng chỉ có thể được null. Hàm tạo cá thể là riêng tư (và nếu ai đó cố gắng gọi hàm tạo riêng này thông qua phản chiếu, một ngoại lệ sẽ được ném vào họ).


Kể từ khi các bộ giá trị được giới thiệu (2017, .NET 4.7), có thể tự nhiên khi sử dụng cấu trúcValueTuple (bộ 0-tuple, biến thể không chung chung) thay vì như vậy MyVoid. Ví dụ của nó có một ToString()trả về "()", vì vậy nó trông giống như một bộ giá trị 0. Kể từ phiên bản C # hiện tại, bạn không thể sử dụng các mã thông báo ()trong mã để lấy một phiên bản. Bạn có thể sử dụng default(ValueTuple)hoặc chỉ default(khi loại có thể được suy ra từ ngữ cảnh) để thay thế.


2
Một tên khác cho điều này sẽ là mẫu đối tượng rỗng (mẫu thiết kế).
Aelphaeis

@Aelphaeis Khái niệm này hơi khác một chút so với mẫu đối tượng rỗng. Ở đây, vấn đề là chỉ có một số loại mà chúng ta có thể sử dụng với một loại chung. Mục tiêu với mẫu đối tượng null là tránh viết một phương thức trả về null để chỉ ra một trường hợp đặc biệt và thay vào đó trả về một đối tượng thực tế với hành vi mặc định thích hợp.
Andrew Palmer

8

Tôi thích ý tưởng của Aleksey Bykov ở trên, nhưng nó có thể được đơn giản hóa một chút

public sealed class Nothing {
    public static Nothing AtAll { get { return null; } }
}

Như tôi không thấy lý do rõ ràng tại sao Nothing.AtAll không thể chỉ đưa ra giá trị rỗng

Ý tưởng tương tự (hoặc ý tưởng của Jeppe Stig Nielsen) cũng rất tuyệt để sử dụng với các lớp đã định.

Ví dụ: nếu kiểu chỉ được sử dụng để mô tả các đối số cho một thủ tục / hàm được truyền làm đối số cho một số phương thức và bản thân nó không nhận bất kỳ đối số nào.

(Bạn vẫn cần phải tạo một trình bao bọc giả hoặc cho phép tùy chọn "Không có gì". Nhưng IMHO việc sử dụng lớp có vẻ tốt với myClass <Nothing>)

void myProcWithNoArguments(Nothing Dummy){
     myProcWithNoArguments(){
}

hoặc là

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}

1
Giá trị nullcó nghĩa là thiếu hoặc vắng mặt object. Chỉ có một giá trị cho một giá trị Nothingcó nghĩa là nó không bao giờ giống bất kỳ thứ gì khác.
LexH

Đó là một ý kiến ​​hay, nhưng tôi đồng ý với @LexieHankins. Tôi nghĩ tốt hơn là "không có gì cả" là một phiên bản duy nhất của lớp Nothingđược lưu trữ trong trường tĩnh riêng và thêm một phương thức khởi tạo riêng. Thực tế là rỗng vẫn còn có thể là gây phiền nhiễu, nhưng điều đó sẽ được giải quyết, hy vọng với C # 8.
Kirk Woll

Về mặt học thuật, tôi hiểu sự khác biệt, nhưng đó sẽ là một trường hợp rất đặc biệt khi sự khác biệt quan trọng. (Hãy tưởng tượng một chức năng của loại nullable chung, nơi sự trở lại của "null" được sử dụng như một dấu hiệu đặc biệt, ví dụ như một số loại marker lỗi)
Eske Rahn

4

void, mặc dù một kiểu, chỉ có giá trị như một kiểu trả về của một phương thức.

Không có cách nào xung quanh hạn chế này của void.


1

Những gì tôi hiện đang làm là tạo các kiểu niêm phong tùy chỉnh với phương thức khởi tạo riêng. Điều này tốt hơn là ném các ngoại lệ trong c-tor vì bạn không cần phải đợi đến thời gian chạy để tìm ra tình huống không chính xác. Nó tốt hơn một cách tinh tế so với việc trả về một trường hợp tĩnh vì bạn không phải cấp phát dù chỉ một lần. Nó tốt hơn một cách tinh tế so với trả về static null vì nó ít dài dòng hơn về phía lời gọi. Điều duy nhất mà người gọi có thể làm là đưa ra null.

public sealed class Void {
    private Void() { }
}

public sealed class None {
    private None() { }
}
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.