Tại sao sử dụng một phương thức công khai trong một lớp nội bộ?


250

Có rất nhiều mã trong một trong các dự án của chúng tôi trông như thế này:

internal static class Extensions
{
    public static string AddFoo(this string s)
    {
        if (s == null)
        {
            return "Foo";
        }

        return $({s}Foo);
    }
}

Có bất kỳ lý do rõ ràng để làm điều này ngoài "dễ dàng hơn để làm cho loại công khai sau này?"

Tôi nghi ngờ nó chỉ quan trọng trong các trường hợp cạnh rất lạ (phản chiếu trong Silverlight) hoặc hoàn toàn không.


1
Theo kinh nghiệm của tôi, đó là cách làm thông thường.
phoog

2
@phoog, được rồi. nhưng tại sao nó là thực tế bình thường? Có lẽ chỉ dễ dàng hơn sau đó thay đổi một loạt các phương pháp để nội bộ? Sửa nhanh -> đánh dấu loại nội bộ thay thế?
bopapa_1979

đó là những gì tôi luôn luôn giả định. Tuy nhiên, tôi không có phân tích kỹ lưỡng, đó là lý do tại sao tôi không đăng câu trả lời.
phoog

2
Câu trả lời của Eric Lippert tóm tắt suy nghĩ của tôi rất tốt, và cũng đưa ra một điều gì đó đang ẩn giấu trong não tôi khi cố gắng tìm ra lối thoát: việc triển khai ngầm các thành viên giao diện phải được công khai.
phoog

Phương pháp đó có bằng return s + "Foo";không? Các +nhà điều hành không quan tâm về null hoặc chuỗi rỗng.
Mikkel R. Lund

Câu trả lời:


418

CẬP NHẬT: Câu hỏi này là chủ đề của blog của tôi vào tháng 9 năm 2014 . Cảm ơn vì câu hỏi tuyệt vời của bạn!

Có một cuộc tranh luận đáng kể về câu hỏi này ngay cả trong chính nhóm biên dịch.

Trước hết, thật khôn ngoan khi hiểu các quy tắc. Một thành viên công khai của một lớp hoặc struct là một thành viên có thể truy cập được vào bất cứ thứ gì có thể truy cập vào kiểu chứa . Vì vậy, một thành viên của một lớp nội bộ có hiệu quả nội bộ.

Vì vậy, bây giờ, với một lớp nội bộ, các thành viên của nó mà bạn muốn truy cập trong hội đồng nên được đánh dấu là công khai hay nội bộ?

Ý kiến ​​của tôi là: đánh dấu các thành viên như công chúng.

Tôi sử dụng "công khai" có nghĩa là "thành viên này không phải là một chi tiết thực hiện". Một thành viên được bảo vệ là một chi tiết thực hiện; có một cái gì đó về nó sẽ là cần thiết để làm cho một lớp dẫn xuất hoạt động. Một thành viên nội bộ là một chi tiết thực hiện; một cái gì đó khác bên trong hội đồng này cần thành viên để hoạt động chính xác. Một thành viên công cộng cho biết "thành viên này đại diện cho khóa, chức năng tài liệu được cung cấp bởi đối tượng này."

Về cơ bản, thái độ của tôi là: giả sử tôi quyết định biến lớp nội bộ này thành một lớp công khai. Để làm điều đó, tôi muốn thay đổi chính xác một điều : khả năng tiếp cận của lớp. Nếu biến một lớp nội bộ thành một lớp công khai có nghĩa là tôi cũng phải biến một thành viên nội bộ thành một thành viên công cộng, thì thành viên đó là một phần của khu vực công cộng của lớp, và nó phải được công khai ngay từ đầu.

Người khác không đồng ý. Có một đội ngũ nói rằng họ muốn có thể liếc qua tuyên bố của một thành viên và ngay lập tức biết liệu nó sẽ được gọi chỉ từ mã nội bộ.

Thật không may, điều đó không phải lúc nào cũng diễn ra tốt đẹp; ví dụ, một lớp bên trong thực hiện giao diện nội bộ vẫn phải có các thành viên triển khai được đánh dấu là công khai, bởi vì chúng là một phần của bề mặt công khai của lớp .


11
Tôi thích câu trả lời này ... nó rất phù hợp với thái độ của tôi đối với việc viết mã tự viết tài liệu ở mức độ lớn nhất có thể, và phạm vi là một cách tuyệt vời để phát sóng ý định, đặc biệt là nếu các thành viên khác trong nhóm của bạn hiểu quy ước của bạn.
bopapa_1979

10
+1 để nói những gì tôi muốn nói nhưng hình thành nó tốt hơn nhiều so với khả năng của tôi (cũng để tăng góc giao diện, mặc dù tôi lưu ý rằng điều đó chỉ đúng với việc triển khai thành viên ngầm).
phoog

2
Phần Giao diện rất quan trọng - không thể thực hiện phương thức giao diện bằng cách sử dụng nội bộ, đó là khai báo giao diện công khai hoặc rõ ràng. Vì vậy, từ công khai bị quá tải với hai nghĩa.
Michael Stum

8
Từ quan điểm ý tưởng, lập luận của bạn ủng hộ publiclà rất thuyết phục. Tuy nhiên, tôi thấy "không phải lúc nào cũng hoạt động tốt ..." là đặc biệt mạnh mẽ. Mất tính nhất quán làm mất giá trị bất kỳ lợi ích "trong nháy mắt". Sử dụng internaltheo cách này có nghĩa là cố tình xây dựng trực giác đôi khi sai. Có một trực giác gần như đúng là IMO là một điều khủng khiếp cần có cho lập trình.
Brian

3
Trích dẫn quan trọng từ bài đăng trên blog của bạn: "Lời khuyên của tôi là thảo luận về vấn đề giữa nhóm của bạn, đưa ra quyết định và sau đó bám sát nó."
bopapa_1979

17

Nếu lớp là internal, nó không quan trọng từ quan điểm tiếp cận cho dù bạn đánh dấu một phương thức internalhay public. Tuy nhiên, vẫn tốt khi sử dụng loại bạn sẽ sử dụng nếu lớp đó là public.

Trong khi một số người đã nói rằng điều này giúp giảm bớt sự chuyển đổi từ internalsang public. Nó cũng phục vụ như là một phần của mô tả phương pháp. Internalcác phương thức thường được coi là không an toàn cho truy cập không bị chặn, trong khi publiccác phương thức được coi là (hầu hết) trò chơi miễn phí.

Bằng cách sử dụng internalhoặc publicnhư bạn sẽ làm trong một publiclớp, bạn đảm bảo rằng bạn đang truyền đạt kiểu truy cập nào được mong đợi, đồng thời giảm bớt công việc cần thiết để tạo ra lớp publictrong tương lai.


Tôi có xu hướng giới hạn mọi thứ ở mức độ lớn nhất có thể trong khi tạo ra một API tốt và cho phép người tiêu dùng dự định của tôi làm những gì tôi dự định họ làm. Tôi cũng cùng bạn đánh dấu thành viên như tôi sẽ làm nếu lớp học được công khai. Cụ thể hơn, tôi chỉ đánh dấu bất kỳ thành viên nào nếu tôi thấy nó luôn an toàn. Nếu không, người khác đánh dấu lớp công khai sau này (nó xảy ra) có thể phơi bày các thành viên không an toàn.
bopapa_1979

@Eric: Nếu bạn muốn từ bỏ việc xác định độ an toàn của phương pháp cho đến khi bạn sẵn sàng thì vẫn ổn, nhưng tôi chỉ đề cập đến các phương pháp được coi là an toàn, trong trường hợp đó không có phơi bày.
Guvante

10

Tôi thường đánh dấu các phương thức của mình trong các lớp bên trong công khai thay vì nội bộ là a) nó không thực sự quan trọng và b) Tôi sử dụng nội bộ để chỉ ra rằng phương thức này là mục đích nội bộ (có một số lý do tại sao tôi không muốn phơi bày điều này Vì vậy, nếu tôi có một phương thức bên trong, tôi thực sự phải hiểu lý do tại sao nó lại bên trong trước khi thay đổi nó thành công khai trong khi nếu tôi đang xử lý một phương thức công khai trong một lớp bên trong thì tôi thực sự phải suy nghĩ về lý do tại sao lớp là nội bộ trái ngược với lý do tại sao mỗi phương thức là nội bộ.


Cảm ơn câu trả lời. Mặc dù vậy, tôi có xu hướng ngoan cố khẳng định rằng "mọi thứ" đều quan trọng khi mã hóa :)
bopapa_1979

1
Bạn đúng Eric, đó là một chút bình luận ném đi. Hy vọng rằng phần còn lại của câu trả lời của tôi là một số sử dụng. Tôi nghĩ câu trả lời của Eric Lippert là những gì tôi đã cố gắng bày tỏ nhưng anh ấy đã giải thích nó tốt hơn nhiều.
Ɖihua eezeƦ

8

Tôi nghi ngờ rằng "nó dễ dàng hơn để loại công khai sau này?" Là nó.

Các quy tắc phạm vi có nghĩa là phương thức sẽ chỉ hiển thị dưới dạng internal- vì vậy nó thực sự không quan trọng cho dù các phương thức được đánh dấu publichay internal.

Một khả năng xuất hiện trong đầu là lớp đã được công khai và sau đó được đổi thành internalvà nhà phát triển đã không buồn thay đổi tất cả các công cụ sửa đổi khả năng truy cập phương thức.


1
+1 để đóng đinh kịch bản "lớp công khai trở thành nội bộ" rõ ràng.
bopapa_1979

8

Trong một số trường hợp, cũng có thể kiểu nội bộ thực hiện giao diện chung, điều đó có nghĩa là mọi phương thức được xác định trên giao diện đó vẫn cần phải được khai báo là công khai.


2

Tương tự, phương thức công khai sẽ thực sự được đánh dấu là nội bộ vì nó nằm trong một lớp bên trong, nhưng nó có một lợi thế (như bạn đã mời), nếu bạn muốn đánh dấu lớp là công khai, bạn phải thay đổi ít mã hơn.


2
đã có một câu hỏi liên quan: stackoverflow.com/questions/711361/
Khăn

0

internalcho biết thành viên chỉ có thể được truy cập từ trong cùng một hội đồng. Các lớp khác trong hội đồng đó có thể truy cập internal publicthành viên, nhưng sẽ không thể truy cập vào một privatehoặc protectedthành viên, internalhoặc không.


Nhưng nếu các phương thức được đánh dấu internalthay vì public, khả năng hiển thị của chúng sẽ không thay đổi. Tôi tin rằng đó là những gì OP đang hỏi về.
Oded

1
@zapthinatingbat - bài viết thực sự chính xác, nhưng nó thực sự trả lời câu hỏi?
Oded

1
Đúng, câu hỏi là TẠI SAO đánh dấu chúng theo cách đó. Tôi hiểu những điều cơ bản của phạm vi. Cảm ơn cho sự cố gắng, mặc dù!
bopapa_1979

0

Tôi thực sự vật lộn với điều này ngày hôm nay. Cho đến bây giờ tôi đã nói rằng tất cả các phương thức nên được đánh dấu internalnếu lớp đó đã internalvà sẽ xem xét bất cứ điều gì khác chỉ đơn giản là mã hóa xấu hoặc sự lười biếng, đặc biệt là trong phát triển doanh nghiệp; tuy nhiên, tôi đã phải phân lớp một publiclớp và ghi đè một trong các phương thức của nó:

internal class SslStreamEx : System.Net.Security.SslStream
{
    public override void Close()
    {
        try
        {
            // Send close_notify manually
        }
        finally
        {
            base.Close();
        }
    }
}

Phương pháp PHẢI là publicvà tôi nghĩ rằng thực sự không có điểm logic nào để thiết lập các phương thức internaltrừ khi chúng thực sự phải như vậy, như Eric Lippert nói.

Cho đến bây giờ tôi chưa bao giờ thực sự dừng lại để nghĩ về nó, tôi chỉ chấp nhận nó, nhưng sau khi đọc bài đăng của Eric, nó thực sự khiến tôi suy nghĩ và sau khi cân nhắc rất nhiều, nó rất có ý nghĩa.


0

Có một sự khác biệt. Trong dự án của chúng tôi, chúng tôi đã thực hiện rất nhiều lớp bên trong, nhưng chúng tôi thực hiện kiểm tra đơn vị trong một tổ hợp khác và trong thông tin lắp ráp của chúng tôi, chúng tôi đã sử dụng InternalsVisibleTo để cho phép tổ hợp UnitTest gọi các lớp bên trong. Tôi đã nhận thấy nếu lớp bên trong có một hàm tạo bên trong, chúng tôi không thể tạo cá thể bằng Activator.CreateInstance trong tập hợp thử nghiệm đơn vị vì một số lý do. Nhưng nếu chúng ta thay đổi hàm tạo thành công khai nhưng lớp vẫn là nội bộ thì nó hoạt động tốt. Nhưng tôi đoán đây là một trường hợp rất hiếm (Giống như Eric đã nói trong bài gốc: Reflection).


0

Tôi nghĩ rằng tôi có một ý kiến ​​bổ sung về điều này. Lúc đầu, tôi đã tự hỏi về việc làm thế nào có ý nghĩa khi tuyên bố một cái gì đó cho công chúng trong một lớp nội bộ. Sau đó tôi đã kết thúc ở đây, đọc rằng nó có thể tốt nếu sau đó bạn quyết định thay đổi lớp thành công khai. Thật. Vì vậy, một mô hình hình thành trong tâm trí của tôi: Nếu nó không thay đổi hành vi hiện tại, thì hãy cho phép và cho phép những thứ không có ý nghĩa (và không bị tổn thương) trong trạng thái mã hiện tại, nhưng sau đó, nếu bạn thay đổi khai báo của lớp.

Như thế này:

public sealed class MyCurrentlySealedClass
{
    protected void MyCurretlyPrivateMethod()
    {
    }
}

Theo "mẫu" tôi đã đề cập ở trên, điều này sẽ hoàn toàn tốt. Nó theo cùng một ý tưởng. Nó hoạt động như một privatephương thức, vì bạn không thể kế thừa lớp. Nhưng nếu bạn xóa sealedràng buộc, nó vẫn hợp lệ: các lớp được kế thừa có thể thấy phương thức này, đó hoàn toàn là những gì tôi muốn đạt được. Nhưng bạn nhận được một cảnh báo : CS0628, hoặc CA1047. Cả hai đều không tuyên bố protectedthành viên trong một sealedlớp. Hơn nữa, tôi đã tìm thấy thỏa thuận đầy đủ, về điều đó là vô nghĩa: 'Thành viên được bảo vệ trong cảnh báo của lớp kín' (một lớp đơn)

Vì vậy, sau cảnh báo này và cuộc thảo luận được liên kết, tôi đã quyết định biến mọi thứ thành nội bộ hoặc ít hơn, trong một lớp bên trong, bởi vì nó phù hợp với kiểu suy nghĩ đó hơn và chúng ta không trộn lẫn các "mẫu" khác nhau.


Tôi cực kỳ thích làm theo cách đó (hoặc ít nhất là vì lý do) Eric Lippert nói. Bài viết của ông chắc chắn là đáng đọc.
bopapa_1979
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.