Praktyczne zastosowania słowa kluczowego „wewnętrznego” w języku C #


420

Czy możesz wyjaśnić, jakie jest praktyczne zastosowanie internalsłowa kluczowego w języku C #?

Wiem, że internalmodyfikator ogranicza dostęp do bieżącego zestawu, ale kiedy iw jakich okolicznościach powinienem go użyć?

Odpowiedzi:


379

Klasy / metody pomocnicze lub pomocnicze, do których chcesz uzyskać dostęp z wielu innych klas w tym samym zestawie, ale które chcesz mieć pewność, że kod w innych zestawach nie będzie miał dostępu.

Z MSDN (przez archive.org):

Powszechnym zastosowaniem dostępu wewnętrznego jest opracowywanie oparte na komponentach, ponieważ umożliwia grupie komponentów prywatną współpracę bez narażania się na resztę kodu aplikacji. Na przykład struktura do tworzenia graficznych interfejsów użytkownika może zapewniać klasy Control i Form, które współpracują przy użyciu elementów z dostępem wewnętrznym. Ponieważ te elementy są wewnętrzne, nie są narażone na kod korzystający z frameworka.

Możesz również użyć modyfikatora wewnętrznego wraz z InternalsVisibleToatrybutem poziomu zestawu, aby utworzyć zestawy „przyjacielskie”, które mają specjalny dostęp do wewnętrznych klas zestawu docelowego.

Może to być przydatne do tworzenia zespołów testujących jednostki, które następnie mogą wywoływać wewnętrzne elementy zespołu do testowania. Oczywiście żaden inny zestaw nie ma takiego poziomu dostępu, więc po zwolnieniu systemu hermetyzacja jest utrzymywana.


8
Atrybut InternalsVisibleTo to świetna pomoc. Dziękuję Ci.
erict

33
Mam wrażenie, że od momentu, kiedy potrzebujesz wewnętrznej, coś jest nie tak z projektem. IMHO powinno być w stanie używać czystego OO do rozwiązywania wszystkich problemów. W tej chwili wpatruję się w około milionowe użycie wewnętrznego w źródle .NET Framework, uniemożliwiając mi wykorzystywanie rzeczy, których sami używają. Dzięki zastosowaniu wewnętrznego stworzyli monolit, który jest powierzchownie modułowy, ale psuje się, gdy próbujesz izolować rzeczy. Ponadto bezpieczeństwo nie jest argumentem, ponieważ ratunkiem jest refleksja.
Grimace of Despair

26
@GrimaceofDespair: Pozwól, że dodam tutaj diagreeing comment. Osobiście uważam wewnętrzną za bardzo przydatną wersję „publicznego” modyfikatora w dużych rozwiązaniach obejmujących wiele projektów. Dodanie tego modyfikatora do klasy w moim podprojekcie zabrania „losowego użycia” tej klasy przez inne podprojekty w rozwiązaniu i umożliwia mi wymuszenie właściwego użycia mojej biblioteki. Jeśli będę musiał później dokonać refaktoryzacji, nie będę musiał zadawać sobie pytania „poczekaj, ale dlaczego ten facet poszedł i stworzył instancję klasy Point, której potrzebowałem tylko do własnych celów w ramach tego konkretnego obliczenia”?
KT.

3
Innymi słowy, jeśli wszyscy polegaliby na wewnętrznych elementach SmtpClient i w pewnym momencie wykryto tam błąd, .NET miałby poważne problemy z jego usunięciem, być może nawet utrzymując go przez długi czas. Pamiętam, że kiedyś przeczytałem zabawny artykuł, który opisywał długość, do której musieli dążyć programiści Windows, aby zachować „kompatybilność błędów” ze wszystkimi starszymi programami, które korzystały z różnych wewnętrznych funkcji i błędów. Powtarzanie tego błędu w .NET nie ma znaczenia.
KT.

13
Myślę, że wewnętrzny jest znacznie bardziej użyteczny niż publiczny. Jedynie raz używasz publicznego, kiedy wyraźnie mówisz „Tutaj, użytkownik - dostarczam użyteczną funkcję do użycia”. Jeśli piszesz aplikację, a nie bibliotekę użytkownika, wszystko powinno być wewnętrzne, ponieważ nie próbujesz w ogóle dawać ludziom żadnych funkcji do wywoływania z zewnątrz. Jeśli piszesz bibliotekę użytkownika, to tylko przydatne funkcje, które spodziewasz się udzielić, utrzymywać, wspierać i utrzymywać do końca czasu, powinny być publiczne. W przeciwnym razie zrób to wewnętrznie.
Darrell Plank,

85

Jeśli Bob potrzebuje BigImportantClass, wtedy Bob musi przekonać ludzi, którzy posiadają projekt A, do zarejestrowania się, aby zagwarantować, że BigImportantClass zostanie napisany, aby zaspokoić jego potrzeby, przetestowany pod kątem spełnienia jego potrzeb, udokumentowany jako spełniający jego potrzeby i proces zostanie wprowadzony, aby zapewnić, że nigdy nie zostanie zmieniony, aby nie spełniał już jego potrzeb.

Jeśli klasa jest wewnętrzna, to nie musi przechodzić przez ten proces, co oszczędza budżet na Projekt A, który mogą wydać na inne rzeczy.

Nie chodzi o to, że wewnętrzne utrudnia życie Boba. Dzięki temu możesz kontrolować, jakie drogie obietnice składa projekt A pod względem funkcji, żywotności, kompatybilności i tak dalej.


5
Ma sens. Żałuję tylko, że nie jesteśmy w stanie zastąpić wewnętrznych członków, ponieważ wszyscy tutaj zgadzamy się na dorosłych.
cwallenpoole,

6
@EricLippert Niezupełnie o to mi chodziło. Jeśli odziedziczę skompilowany kod, który zawiera „wewnętrzny”, utknąłem, prawda? Nie można po prostu powiedzieć kompilatorowi: „Nie, ten inny programista cię okłamał. Zamiast tego powinieneś mi zaufać”, chyba że użyję refleksji.
cwallenpoole,

11
@cwallenpoole: Jeśli używasz kodu napisanego przez kłamców, którzy piszą kod, którego nie lubisz, przestań go używać i napisz własny kod, który ci się bardziej podoba.
Eric Lippert,

2
@EricLippert To miał być język w policzek, nie chciałem obrażać. (W takich przypadkach próbowałem głównie potwierdzić, że jestem SOL).
cwallenpoole

5
@cwallenpoole: Ja przynajmniej nie jestem obrażony. Oferuję dobrą radę, jeśli utkniesz w tej niefortunnej sytuacji.
Eric Lippert,

60

Innym powodem użycia wewnętrznego jest zaciemnienie plików binarnych. Obfuscator wie, że szyfrowanie nazw klas dowolnych klas wewnętrznych jest bezpieczne, podczas gdy nazw klas publicznych nie można mieszać, ponieważ mogłoby to zniszczyć istniejące odniesienia.


4
Pff. Patrząc na kod bajtowy za pomocą deasemblera, nadal jasno widać, co robi kod. Obfuskatory są zaledwie łagodnym środkiem odstraszającym dla każdego, kto naprawdę próbuje dostać się do wnętrza. Nigdy nie słyszałem o hakerze, który zatrzymał się tylko dlatego, że nie otrzymał żadnych przydatnych nazw funkcji.
Nyerguds,

28
więc nie zamykasz drzwi podczas snu, ponieważ nigdy nie słyszałeś o złodzieja zatrzymanego przez zamek?
Sergio,

4
Dlatego szyfruję nazwy klas i zmiennych w kodzie źródłowym. Możesz być w stanie dowiedzieć się, co to jest PumpStateComparator, ale możesz zgadnąć, co to jest LpWj4mWE? #jobsecurity (Nie robię tego i proszę, tak naprawdę nie rób tego, internetowi).
Slothario,

32

Jeśli piszesz bibliotekę DLL, która zawiera tonę złożonej funkcjonalności w prostym publicznym interfejsie API, wówczas „wewnętrzny” jest używany dla członków klasy, których nie należy ujawniać publicznie.

Ukrywanie złożoności (inaczej enkapsulacja) to główna koncepcja inżynierii oprogramowania wysokiej jakości.


20

Wewnętrzne słowo kluczowe jest szeroko stosowane, gdy budujesz opakowanie na niezarządzanym kodzie.

Gdy masz bibliotekę opartą na C / C ++, którą chcesz DllImport, możesz zaimportować te funkcje jako funkcje statyczne klasy i uczynić je wewnętrznymi, aby użytkownik miał dostęp tylko do opakowania, a nie oryginalnego interfejsu API, więc nie może zadzierać z czymkolwiek. Funkcje są statyczne, można ich używać wszędzie w zespole, dla wielu potrzebnych klas opakowań.

Możesz rzucić okiem na Mono.Cairo, jest to opakowanie wokół biblioteki Cairo, która wykorzystuje to podejście.


12

Kierując się zasadą „używaj możliwie ścisłego modyfikatora”, używam wszędzie tam, gdzie potrzebuję dostępu, powiedzmy, metody z innej klasy, dopóki nie będę musiał uzyskać do niej dostępu z innego zestawu.

Ponieważ interfejs asemblera jest zwykle węższy niż suma interfejsów jego klas, używam go w wielu miejscach.


3
Jeśli „zastosujesz jak najściślejszy modyfikator”, to dlaczego nie prywatny?
R. Martinho Fernandes,

6
Ponieważ private jest zbyt rygorystyczne, gdy do właściwości / metod klasy należy uzyskiwać dostęp poza klasą, a przypadki, w których należy uzyskać do nich dostęp z innej strony, bly nie są tak często.
Ilya Komakhin,

11

Uważam, że wewnętrzne jest zdecydowanie zbyt często wykorzystywane. naprawdę nie powinieneś wystawiać pewnych funkcji tylko na pewne klasy, których nie zrobiłbyś innym konsumentom.

To, moim zdaniem, psuje interfejs, psuje abstrakcję. Nie oznacza to, że nigdy nie należy go używać, ale lepszym rozwiązaniem jest refaktoryzacja do innej klasy lub użycie, jeśli to możliwe, w inny sposób. Jednak nie zawsze jest to możliwe.

Przyczyną problemów może być to, że inny programista może zostać obciążony budowaniem innej klasy w tym samym zestawie, co twój. Posiadanie elementów wewnętrznych zmniejsza jasność abstrakcji i może powodować problemy, jeśli zostanie niewłaściwie użyte. Byłby to ten sam problem, jakbyś go upublicznił. Druga klasa budowana przez innego programistę jest nadal konsumentem, tak jak każda klasa zewnętrzna. Abstrakcja i enkapsulacja klas nie służy tylko ochronie dla / przed klasami zewnętrznymi, ale dla każdej i wszystkich klas.

Innym problemem jest to, że wielu programistów pomyśli, że mogą potrzebować użyć go gdzie indziej w asemblerze i oznaczyć go jako wewnętrzny, mimo że w danym momencie tego nie potrzebują. Inny deweloper może więc pomyśleć, że jest tam do wzięcia. Zazwyczaj chcesz oznaczyć jako prywatny, dopóki nie będziesz mieć ostatecznej potrzeby.

Ale niektóre z nich mogą być subiektywne i nie twierdzę, że nigdy nie należy ich używać. Po prostu użyj w razie potrzeby.


Często buduję obiekty domeny, do których mają członków, które powinny być publiczne dla innych obiektów domeny w tym samym zestawie. Jednak ci sami członkowie NIE powinni być dostępni dla kodu klienta spoza zestawu. W takich sytuacjach „wewnętrzny” jest bardzo odpowiedni.
Rock Anthony Johnson

8

Ciekawego widziałem kiedyś, może tydzień, na blogu, którego nie pamiętam. Zasadniczo nie mogę tego przypisać, ale pomyślałem, że może mieć jakąś przydatną aplikację.

Powiedzmy, że chciałeś, aby klasa abstrakcyjna była widoczna dla innego zestawu, ale nie chcesz, aby ktoś mógł po niej dziedziczyć. Sealed nie będzie działać, ponieważ jest z jakiegoś powodu abstrakcyjny, inne klasy w tym zestawie dziedziczą po nim. Prywatne nie będzie działać, ponieważ możesz chcieć zadeklarować klasę nadrzędną gdzieś w innym zestawie.

przestrzeń nazw Base.Assembly
{
  publiczna klasa abstrakcyjna Rodzic
  {
    wewnętrzne streszczenie void SomeMethod ();
  }

  // Działa to dobrze, ponieważ jest w tym samym zestawie.
  klasa publiczna ChildWithin: Parent
  {
    przesłonięcie wewnętrzne void SomeMethod ()
    {
    }
  }
}

przestrzeń nazw Another.Assembly
{
  // Kaboom, ponieważ nie można przesłonić metody wewnętrznej
  klasa publiczna ChildOutside: Parent
  {
  }

  test klasy publicznej 
  { 

    //W porządku
    prywatny rodzic _parent;

    test publiczny ()
    {
      //Nadal dobrze
      _parent = new ChildWithin ();
    }
  }
}

Jak widać, skutecznie pozwala komuś korzystać z klasy Parent bez możliwości dziedziczenia.


7

Ten przykład zawiera dwa pliki: Assembly1.cs i Assembly2.cs. Pierwszy plik zawiera wewnętrzną klasę bazową BaseClass. W drugim pliku próba utworzenia instancji BaseClass spowoduje błąd.

// Assembly1.cs
// compile with: /target:library
internal class BaseClass 
{
   public static int intM = 0;
}

// Assembly1_a.cs
// compile with: /reference:Assembly1.dll
class TestAccess 
{
   static void Main()
   {  
      BaseClass myBase = new BaseClass();   // CS0122
   }
}

W tym przykładzie użyj tych samych plików, których użyto w przykładzie 1, i zmień poziom dostępności BaseClass na publiczny . Zmień także poziom dostępności elementu IntM na wewnętrzny . W takim przypadku można utworzyć instancję klasy, ale nie można uzyskać dostępu do elementu wewnętrznego.

// Assembly2.cs
// compile with: /target:library
public class BaseClass 
{
   internal static int intM = 0;
}

// Assembly2_a.cs
// compile with: /reference:Assembly1.dll
public class TestAccess 
{
   static void Main() 
   {      
      BaseClass myBase = new BaseClass();   // Ok.
      BaseClass.intM = 444;    // CS0117
   }
}

źródło : http://msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx


6

Gdy masz metody, klasy itp., Które muszą być dostępne w ramach bieżącego zestawu, a nigdy poza nim.

Na przykład DAL może mieć ORM, ale obiekty nie powinny być narażone na warstwę biznesową, wszystkie interakcje powinny być wykonywane metodami statycznymi i przekazywanie wymaganych parametrów.


6

Bardzo interesującym zastosowaniem wewnętrznego - z wewnętrznym elementem ograniczonym oczywiście tylko do zestawu, w którym jest zadeklarowany - jest do pewnego stopnia uzyskanie funkcji „przyjaciela”. Członek-przyjaciel jest czymś, co jest widoczne tylko dla niektórych innych zespołów poza zespołem, w którym zostało zadeklarowane. C # nie ma wbudowanego wsparcia dla przyjaciela, jednak CLR tak.

Możesz użyć InternalsVisibleToAttribute do zadeklarowania zestawu znajomych, a wszystkie odniesienia z zestawu znajomych będą traktować wewnętrznych członków twojego deklarującego zestawu jako publiczne w zakresie zestawu znajomych. Problem polega na tym, że wszystkie elementy wewnętrzne są widoczne; nie możesz wybrać.

Dobrym zastosowaniem dla InternalsVisibleTo jest wystawienie różnych elementów wewnętrznych na zespół testowy jednostki, eliminując w ten sposób potrzebę skomplikowanych prac związanych z odbiciem w celu przetestowania tych elementów. Wszyscy członkowie wewnętrzni, którzy są widoczni, nie stanowią większego problemu, jednak takie podejście dość mocno psuje interfejsy klas i może potencjalnie zrujnować enkapsulację wewnątrz deklarującego zestawu.


5

Zasadniczo istnieją dwa rodzaje członków:

  • powierzchnia publiczna : widoczna z zewnętrznego zestawu (publiczny, chroniony i chroniony wewnętrznie): osoba dzwoniąca nie jest zaufana, dlatego konieczna jest walidacja parametrów, dokumentacja metody itp.
  • prywatna powierzchnia : niewidoczny z zewnętrznego zestawu (prywatnego i wewnętrznego lub klas wewnętrznych): osoba dzwoniąca jest ogólnie zaufana, więc sprawdzanie parametrów, dokumentacja metody itp. może zostać pominięta.

4

Redukcja szumów, im mniej typów ujawniasz, tym prostsza jest twoja biblioteka. Zabezpieczenie przed manipulacją / Zabezpieczenie to kolejne (choć Odbicie może wygrać z nim).


4

Klasy wewnętrzne umożliwiają ograniczenie interfejsu API zestawu. Ma to zalety, takie jak uproszczenie interfejsu API.

Ponadto, jeśli błąd występuje w twoim zestawie, istnieje mniejsze prawdopodobieństwo, że poprawka wprowadzi przełomową zmianę. Bez klas wewnętrznych należałoby założyć, że zmiana członków publicznych dowolnej klasy byłaby przełomową zmianą. W przypadku klas wewnętrznych można założyć, że modyfikowanie ich publicznych elementów tylko psuje wewnętrzny interfejs API zestawu (i wszelkich zestawów, do których odwołuje się atrybut InternalsVisibleTo).

Lubię mieć enkapsulację na poziomie klasy i na poziomie asemblera. Niektórzy nie zgadzają się z tym, ale miło jest wiedzieć, że ta funkcjonalność jest dostępna.


2

Mam projekt, który używa LINQ-SQL dla zaplecza danych. Mam dwie główne przestrzenie nazw: Biz i Data. Model danych LINQ żyje w danych i jest oznaczony jako „wewnętrzny”; przestrzeń nazw Biz ma klasy publiczne, które otaczają klasy danych LINQ.

Więc jest Data.Clienti Biz.Client; ten ostatni ujawnia wszystkie istotne właściwości obiektu danych, np .:

private Data.Client _client;
public int Id { get { return _client.Id; } set { _client.Id = value; } }

Obiekty Biz mają prywatnego konstruktora (aby wymusić użycie metod fabrycznych) i wewnętrznego konstruktora, który wygląda następująco:

internal Client(Data.Client client) {
    this._client = client;
}

Może to być wykorzystane przez dowolną klasę biznesową w bibliotece, ale interfejs użytkownika nie ma bezpośredniego dostępu do modelu danych, zapewniając, że warstwa biznesowa zawsze działa jako pośrednik.

To pierwszy raz, kiedy naprawdę internaldużo zużyłem i okazuje się to całkiem przydatne.


2

Są przypadki, w których sensowne jest tworzenie członków klas internal. Jednym z przykładów może być kontrolowanie sposobu tworzenia instancji klas; powiedzmy, że udostępniasz jakąś fabrykę do tworzenia instancji klasy. Możesz utworzyć konstruktor internal, aby fabryka (która znajduje się w tym samym zestawie) mogła tworzyć instancje klasy, ale kod poza tym zestawem nie może.

Jednak nie widzę sensu w tworzeniu klas lub członków internalbez konkretnych powodów, tak mało, jak sensowne jest ich tworzenie public, lub privatebez konkretnych powodów.


1

jedyną rzeczą, w której kiedykolwiek użyłem wewnętrznego słowa kluczowego, jest kod sprawdzający licencję w moim produkcie ;-)


1

Jednym z zastosowań wewnętrznego słowa kluczowego jest ograniczenie dostępu do konkretnych implementacji przez użytkownika twojego zestawu.

Jeśli masz fabrykę lub inną centralną lokalizację do konstruowania obiektów, użytkownik twojego zestawu musi mieć do czynienia tylko z interfejsem publicznym lub abstrakcyjną klasą bazową.

Ponadto konstruktory wewnętrzne pozwalają kontrolować, gdzie i kiedy tworzy się instancję klasy publicznej.


1

Co powiesz na to: zazwyczaj zaleca się, aby nie wystawiać obiektu List zewnętrznym użytkownikom zestawu, a raczej wystawiać IEnumerable. Ale o wiele łatwiej jest używać obiektu List wewnątrz zestawu, ponieważ otrzymujesz składnię tablic i wszystkie inne metody List. Tak więc zazwyczaj mam wewnętrzną właściwość odsłaniającą Listę do użycia wewnątrz zestawu.

Mile widziane są komentarze dotyczące tego podejścia.


@Samik - czy mógłbyś rozwinąć tę kwestię: „zazwyczaj zaleca się, aby nie wystawiać obiektu List zewnętrznym użytkownikom zestawu, a raczej wystawiać IEnumerable”. dlaczego jest niezliczona ilość?
Howiecamp

1
@Howiecamp: Głównie dlatego, że IEnumerable daje dostęp tylko do odczytu do listy, przy czym tak, jakby odsłonić listę bezpośrednio, może być modyfikowana przez klientów (np. Usuwanie elementów itp.), Co może być niezamierzone.
Samik R

Ponadto ujawnienie listy lub IList tworzy umowę, na którą zawsze zezwalasz (na przykład) na szybki losowy dostęp do danych. Jeśli któregoś dnia zdecydujesz się zmienić metodę, aby użyć innej kolekcji lub w ogóle jej nie ma (zwrot wydajności), albo musisz utworzyć Listę, aby zwrócić wartość, albo zerwać kontrakt, zmieniając typ zwracanej metody. Korzystanie z mniej konkretnego typu zwrotu daje elastyczność.

1

Pamiętaj, że każda klasa zdefiniowana jako publicautomatycznie pojawi się w inteligencji, gdy ktoś spojrzy na przestrzeń nazw twojego projektu. Z punktu widzenia interfejsu API ważne jest, aby pokazywać użytkownikom projektu tylko te klasy, których mogą używać. Użyj internalsłowa kluczowego, aby ukryć rzeczy, których nie powinny widzieć.

Jeśli twój Big_Important_Classprojekt A jest przeznaczony do użytku poza projektem, nie powinieneś go oznaczać internal.

Jednak w wielu projektach często masz zajęcia, które naprawdę są przeznaczone tylko do użytku w projekcie. Na przykład może istnieć klasa przechowująca argumenty do sparametryzowanego wywołania wątku. W takich przypadkach należy oznaczyć je tak, internaljakby nie z innego powodu niż w celu ochrony przed niezamierzoną zmianą interfejsu API w późniejszym okresie.


Dlaczego więc nie użyć prywatnego?
Więzień ZERO

1
Słowa privatekluczowego można używać tylko w przypadku klas lub struktur zdefiniowanych w innej klasie lub strukturze. Klasa zdefiniowana w przestrzeni nazw może być tylko zadeklarowana publiclub internal.
Matt Davis,

1

Chodzi o to, że kiedy projektujesz bibliotekę, tylko klasy przeznaczone do użytku z zewnątrz (przez klientów twojej biblioteki) powinny być publiczne. W ten sposób możesz ukryć klasy, które

  1. Prawdopodobnie ulegną zmianie w przyszłych wydaniach (gdyby były publiczne, złamałbyś kod klienta)
  2. Są bezużyteczne dla klienta i mogą powodować zamieszanie
  3. Nie są bezpieczne (więc niewłaściwe użycie może poważnie uszkodzić bibliotekę)

itp.

Jeśli opracowujesz rozwiązania wewnętrzne, to użycie elementów wewnętrznych nie jest tak ważne, ponieważ zwykle klienci będą mieli stały kontakt z tobą i / lub dostęp do kodu. Są jednak dość krytyczne dla twórców bibliotek.


-7

Gdy masz klasy lub metody, które nie pasują idealnie do paradygmatu zorientowanego obiektowo, które robią niebezpieczne rzeczy, które trzeba wywoływać z innych klas i metod pod kontrolą, i których nie chcesz pozwalać innym .

public class DangerousClass {
    public void SafeMethod() { }
    internal void UpdateGlobalStateInSomeBizarreWay() { }
}

Co próbujesz powiedzieć? To nie jest bardzo jasne. Przynajmniej nie dla mnie.
pqsk

Uzgodniono z @pqsk
Khan
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.