Usi pratici per la parola chiave "interna" in C #


420

Potresti spiegare qual è l'utilizzo pratico della internalparola chiave in C #?

So che il internalmodificatore limita l'accesso all'assembly corrente, ma quando e in quali circostanze dovrei usarlo?

Risposte:


379

Classi / metodi di utilità o di supporto a cui si desidera accedere da molte altre classi all'interno dello stesso assembly, ma a cui si desidera garantire che il codice in altri assembly non possa accedervi.

Da MSDN (tramite archive.org):

Un uso comune dell'accesso interno è nello sviluppo basato su componenti perché consente a un gruppo di componenti di cooperare in modo privato senza essere esposti al resto del codice dell'applicazione. Ad esempio, un framework per la creazione di interfacce utente grafiche potrebbe fornire classi Control e Form che cooperano utilizzando membri con accesso interno. Poiché questi membri sono interni, non sono esposti al codice che utilizza il framework.

È inoltre possibile utilizzare il modificatore interno insieme InternalsVisibleToall'attributo a livello di assieme per creare assiemi "amici" a cui viene concesso un accesso speciale alle classi interne dell'assieme di destinazione.

Ciò può essere utile per la creazione di assiemi di unit testing che possono quindi chiamare i membri interni dell'assembly da testare. Naturalmente a nessun altro assembly viene concesso questo livello di accesso, quindi quando si rilascia il sistema, l'incapsulamento viene mantenuto.


8
L'attributo InternalsVisibleTo è di grande aiuto. Grazie.
erigere l'

33
La mia sensazione è che dal momento in cui hai bisogno di interni, c'è qualcosa che non va nel design da qualche parte. IMHO, si dovrebbe essere in grado di utilizzare OO puro per risolvere tutti i problemi. In questo preciso momento, sto fissando circa il milionesimo uso di interni nella fonte .NET Framework, impedendo il mio modo di sfruttare le cose che usano loro stessi. Attraverso l'uso di interni, hanno creato un monolite superficialmente modulare, ma si rompe quando si tenta di isolare le cose. Inoltre, la sicurezza non è un argomento, in quanto vi è una riflessione sul salvataggio.
Smorfia di disperazione,

26
@GrimaceofDespair: vorrei aggiungere un commento discordante qui. Personalmente vedo interno come una versione molto utile del modificatore "pubblico" in grandi soluzioni multi-progetto. L'aggiunta di questo modificatore a una classe nel mio sottoprogetto proibisce "l'uso casuale" di questa classe da parte di altri sottoprogetti nella soluzione e mi consente di forzare l'uso corretto della mia libreria. Se avrò bisogno di fare un po 'di refactoring in seguito, non dovrò chiedermi "aspetta, ma perché quel tipo è andato a creare un'istanza della classe Point di cui avevo bisogno solo per i miei scopi all'interno di questo particolare calcolo"?
KT.

3
Per dirla in altro modo, se tutti si affidassero agli interni di SmtpClient, e in un momento fosse stato scoperto un bug lì, .NET avrebbe avuto seri problemi per risolvere quel bug, magari anche conservandolo a lungo. Ricordo di aver letto un articolo divertente una volta che descriveva la lunghezza a cui gli sviluppatori di Windows dovevano andare per mantenere la "compatibilità con i bug" con tutti i programmi più vecchi che utilizzavano varie funzionalità e bug interni. Ripetere questo errore con .NET non è significativo.
KT.

13
Penso che l'interno sia MODO più utile del pubblico. L'unica volta che usi pubblico è quando dici esplicitamente "Qui, utente - ti sto fornendo una funzione utile che puoi usare". Se stai scrivendo un'applicazione piuttosto che una libreria utente, tutto dovrebbe essere interno perché non stai provando a dare alle persone alcuna funzione per chiamare esternamente. Se stai scrivendo una libreria utente, solo le funzioni utili che ti aspetti di offrire all'utente, mantenere, supportare e mantenere fino alla fine dei tempi dovrebbero essere pubbliche. Altrimenti, rendilo interno.
Darrell Plank,

85

Se Bob ha bisogno di BigImportantClass, allora Bob ha bisogno di far iscrivere le persone che possiedono il progetto A per garantire che BigImportantClass sarà scritto per soddisfare le sue esigenze, testato per assicurarsi che soddisfi le sue esigenze, sia documentato come conforme alle sue esigenze e che un processo sarà messo in atto per garantire che non sarà mai cambiato in modo da non soddisfare più le sue esigenze.

Se una classe è interna, non deve passare attraverso quel processo, il che consente di risparmiare budget per il Progetto A che possono spendere per altre cose.

Il punto di interno non è che rende la vita difficile a Bob. È che ti consente di controllare le costose promesse fatte dal Progetto A in merito a funzionalità, durata, compatibilità e così via.


5
Ha perfettamente senso. Spero solo che abbiamo avuto la possibilità di scavalcare i membri interni, perché siamo tutti consenzienti adulti qui.
cwallenpoole,

6
@EricLippert Non è esattamente quello che volevo dire. Se eredito il codice compilato che contiene "interno", sono bloccato, giusto? Non c'è modo di dire semplicemente al compilatore "No, che un altro sviluppatore ti ha mentito. Dovresti invece fidarti di me" se non uso la riflessione.
cwallenpoole,

11
@cwallenpoole: se stai usando codice scritto da bugiardi che scrivono codice che non ti piace, smetti di usare il loro codice e scrivi il tuo codice che ti piace di più.
Eric Lippert,

2
@EricLippert Che avrebbe dovuto essere la lingua sulla guancia, non volevo offendere. (Stavo principalmente cercando di confermare che sono SOL in questi casi).
cwallenpoole,

5
@cwallenpoole: non sono minimamente offeso. Ti sto offrendo buoni consigli se ti dovessi trovare bloccato in quella sfortunata situazione.
Eric Lippert,

60

Un altro motivo per usare internal è se offuschi i tuoi binari. L'offuscatore sa che è sicuro confondere il nome della classe di qualsiasi classe interna, mentre il nome delle classi pubbliche non può essere criptato, perché ciò potrebbe spezzare i riferimenti esistenti.


4
TFP. Guardare il bytecode con un disassemblatore renderà comunque abbastanza chiaro cosa fa il codice. Gli offuscatori sono a malapena un leggero deterrente per chiunque cerchi davvero di entrare negli interni. Non ho mai sentito parlare di un hacker che si è fermato solo perché non ha ottenuto alcun nome di funzione utile.
Nyerguds,

28
quindi non chiudi la porta quando dormi, perché non hai mai sentito parlare di un ladro bloccato da una serratura?
Sergio,

4
Ecco perché scramble i nomi delle classi e delle variabili nel codice sorgente. Potresti essere in grado di capire cos'è un PumpStateComparator, ma puoi indovinare cos'è un LpWj4mWE? #jobsecurity (Non lo faccio, e per favore non farlo, gente di Internet.)
Slothario

32

Se stai scrivendo una DLL che incapsula una tonnellata di funzionalità complesse in una semplice API pubblica, viene utilizzato "interno" sui membri della classe che non devono essere esposti pubblicamente.

Nascondere la complessità (aka incapsulamento) è il concetto principale di ingegneria del software di qualità.


20

La parola chiave interna è molto utilizzata quando si crea un wrapper su codice non gestito.

Quando si dispone di una libreria basata su C / C ++ che si desidera importare, è possibile importare queste funzioni come funzioni statiche di una classe e renderle interne, in modo che l'utente abbia accesso al proprio wrapper e non all'API originale, quindi non può pasticciare con qualsiasi cosa. Le funzioni statiche possono essere utilizzate ovunque nell'assieme, per le classi di wrapper multiple necessarie.

Puoi dare un'occhiata a Mono.Cairo, è un wrapper per la libreria cairo che utilizza questo approccio.


12

Essendo guidato dalla regola "usa il modificatore più rigoroso che puoi" uso interno ovunque devo dire, per esempio, il metodo da un'altra classe fino a quando non ho esplicitamente bisogno di accedervi da un altro assembly.

Poiché l'interfaccia di assemblaggio è di solito più stretta della somma delle sue interfacce di classi, ci sono molti posti in cui la uso.


3
Se "usi il modificatore più rigoroso che puoi" perché non privato allora?
R. Martinho Fernandes,

6
Poiché private è troppo rigoroso quando è necessario accedere alle proprietà / ai metodi della classe al di fuori della classe e ai casi in cui è necessario accedervi da un altro asse, i bly non sono così frequenti.
Ilya Komakhin,

11

Trovo interno molto abusato. non dovresti davvero esporre determinate funzionalità solo a determinate classi che non vorresti ad altri consumatori.

Questo secondo me rompe l'interfaccia, rompe l'astrazione. Questo non vuol dire che non dovrebbe mai essere usato, ma una soluzione migliore è quella di refactoring a una classe diversa o di essere usato in un modo diverso, se possibile. Tuttavia, questo potrebbe non essere sempre possibile.

La ragione per cui può causare problemi è che un altro sviluppatore potrebbe essere incaricato di costruire un'altra classe nello stesso assembly in cui si trova il tuo. Avere degli interni riduce la chiarezza dell'astrazione e può causare problemi se usato in modo improprio. Sarebbe lo stesso problema come se lo rendessi pubblico. L'altra classe che viene creata dall'altro sviluppatore è ancora un consumatore, proprio come qualsiasi classe esterna. L'astrazione e l'incapsulamento delle classi non sono solo per la protezione di / da classi esterne, ma per tutte le classi.

Un altro problema è che molti sviluppatori penseranno che potrebbe essere necessario usarlo altrove nell'assemblaggio e contrassegnarlo comunque come interno, anche se al momento non ne hanno bisogno. Un altro sviluppatore potrebbe pensare che sia lì per la presa. In genere si desidera contrassegnare come privato fino a quando non si ha un'esigenza definitiva.

Ma un po 'di questo può essere soggettivo e non sto dicendo che non dovrebbe mai essere usato. Basta usare quando necessario.


Costruisco spesso oggetti di dominio per i quali hanno membri che dovrebbero essere pubblici ad altri oggetti di dominio all'interno dello stesso assembly. Tuttavia, questi stessi membri NON devono essere disponibili per il codice client esterno all'assembly. Per situazioni come questa, "interno" è molto appropriato.
Rock Anthony Johnson,

8

Ne ho visto uno interessante l'altro giorno, forse la settimana, su un blog che non ricordo. Fondamentalmente non posso prendermi il merito per questo, ma ho pensato che potesse avere qualche utile applicazione.

Supponi di volere che una classe astratta venga vista da un'altra assemblea, ma non vuoi che qualcuno possa ereditarla. Sealed non funzionerà perché è astratto per una ragione, altre classi in quell'assembly ne ereditano. Privato non funzionerà perché potresti voler dichiarare una classe Parent da qualche parte nell'altro assembly.

spazio dei nomi Base.Assembly
{
  genitore di classe astratta pubblica
  {
    vuoto astratto interno SomeMethod ();
  }

  // Funziona bene dato che si trova nello stesso assieme.
  classe pubblica ChildWithin: Parent
  {
    override interno void SomeMethod ()
    {
    }
  }
}

spazio dei nomi Another.Assembly
{
  // Kaboom, perché non è possibile ignorare un metodo interno
  classe pubblica ChildOutside: Parent
  {
  }

  Test di classe pubblica 
  { 

    //Va bene
    genitore privato _parent;

    test pubblico ()
    {
      //Ancora bene
      _parent = new ChildWithin ();
    }
  }
}

Come puoi vedere, consente effettivamente a qualcuno di utilizzare la classe Parent senza essere in grado di ereditare.


7

Questo esempio contiene due file: Assembly1.cs e Assembly2.cs. Il primo file contiene una classe base interna, BaseClass. Nel secondo file, un tentativo di creare un'istanza di BaseClass produrrà un errore.

// 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
   }
}

In questo esempio, utilizzare gli stessi file utilizzati nell'esempio 1 e modificare il livello di accessibilità di BaseClass in pubblico . Modificare anche il livello di accessibilità del membro IntM in interno . In questo caso, è possibile creare un'istanza della classe, ma non è possibile accedere al membro interno.

// 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
   }
}

fonte : http://msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx


6

Quando si hanno metodi, classi, ecc. Che devono essere accessibili nell'ambito dell'assembly corrente e mai al di fuori di esso.

Ad esempio, un DAL può avere un ORM ma gli oggetti non devono essere esposti al livello aziendale. Tutte le interazioni devono essere eseguite tramite metodi statici e passando i parametri richiesti.


6

Un uso molto interessante di internal - con il membro interno ovviamente limitato solo all'assemblea in cui viene dichiarato - sta ottenendo in qualche modo la funzionalità "amico". Un membro amico è qualcosa che è visibile solo a certe altre assemblee al di fuori dell'assemblea in cui è stata dichiarata. C # non ha supporto incorporato per gli amici, tuttavia il CLR lo fa.

È possibile utilizzare InternalsVisibleToAttribute per dichiarare un'assemblea di amici e tutti i riferimenti all'interno dell'assemblea di amici tratteranno i membri interni della propria assemblea dichiarante come pubblici nell'ambito di tale assemblea. Un problema è che tutti i membri interni sono visibili; non puoi scegliere.

Un buon uso per InternalsVisibleTo è quello di esporre vari membri interni a un gruppo di test unitari eliminando così la necessità di complesse soluzioni di riflessione per testare quei membri. La visibilità di tutti i membri interni non è un grosso problema, tuttavia adottare questo approccio compromette le interfacce della classe piuttosto pesantemente e può potenzialmente rovinare l'incapsulamento all'interno dell'assemblea dichiarante.


5

Come regola empirica ci sono due tipi di membri:

  • superficie pubblica : visibile da un assembly esterno (pubblico, protetto e interno protetto): il chiamante non è affidabile, quindi è necessaria la convalida dei parametri, la documentazione del metodo, ecc.
  • superficie privata : non visibile da un assembly esterno (classi private e interne o interne): il chiamante è generalmente attendibile, quindi la convalida dei parametri, la documentazione del metodo, ecc. può essere omessa.

4

Riduzione del rumore, meno tipi si espongono, più semplice è la libreria. La prova di manomissione / Sicurezza è un'altra (sebbene Reflection possa vincere contro di essa).


4

Le classi interne consentono di limitare l'API dell'assembly. Questo ha dei vantaggi, come semplificare la comprensione dell'API.

Inoltre, se esiste un bug nel proprio assieme, c'è meno possibilità che la correzione introduca una rottura. Senza le classi interne, dovresti presumere che cambiare i membri pubblici di qualsiasi classe sarebbe un cambiamento decisivo. Con le classi interne, puoi supporre che la modifica dei loro membri pubblici interrompa solo l'API interna dell'assembly (e tutti gli assembly a cui fa riferimento l'attributo InternalsVisibleTo).

Mi piace avere l'incapsulamento a livello di classe e di assemblaggio. Ci sono alcuni che non sono d'accordo con questo, ma è bello sapere che la funzionalità è disponibile.


2

Ho un progetto che utilizza LINQ-to-SQL per il back-end dei dati. Ho due spazi dei nomi principali: Biz e Data. Il modello di dati LINQ risiede nei dati ed è contrassegnato come "interno"; lo spazio dei nomi Biz ha classi pubbliche che avvolgono le classi di dati LINQ.

Quindi c'è Data.Client, e Biz.Client; quest'ultimo espone tutte le proprietà pertinenti dell'oggetto dati, ad esempio:

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

Gli oggetti Biz hanno un costruttore privato (per forzare l'uso dei metodi di fabbrica) e un costruttore interno che assomiglia a questo:

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

Ciò può essere utilizzato da qualsiasi classe aziendale nella libreria, ma il front-end (UI) non ha modo di accedere direttamente al modello di dati, garantendo che il livello aziendale agisca sempre come intermediario.

Questa è la prima volta che uso davvero internalmolto e si sta dimostrando abbastanza utile.


2

Ci sono casi in cui ha senso rendere membri delle classi internal. Un esempio potrebbe essere se si desidera controllare il modo in cui le classi vengono istanziate; diciamo che fornisci una sorta di factory per la creazione di istanze della classe. È possibile creare il costruttore internal, in modo che la factory (che risiede nello stesso assembly) possa creare istanze della classe, ma il codice al di fuori di tale assembly non può.

Tuttavia, non riesco a capire se fare lezioni o membri internalsenza ragioni specifiche, per quanto abbia senso formarle publico privatesenza ragioni specifiche.


1

l'unica cosa su cui abbia mai usato la parola chiave interna è il codice di controllo della licenza nel mio prodotto ;-)


1

Un uso della parola chiave interna è limitare l'accesso a implementazioni concrete da parte dell'utente dell'assembly.

Se si dispone di una fabbrica o di un'altra posizione centrale per la costruzione di oggetti, l'utente dell'assembly deve occuparsi solo dell'interfaccia pubblica o della classe base astratta.

Inoltre, i costruttori interni consentono di controllare dove e quando viene istanziata una classe altrimenti pubblica.


1

Che ne dici di questo: in genere si consiglia di non esporre un oggetto List agli utenti esterni di un assembly, piuttosto di esporre un IEnumerable. Ma è molto più semplice usare un oggetto List all'interno dell'assembly, perché si ottiene la sintassi dell'array e tutti gli altri metodi List. Quindi, in genere ho una proprietà interna che espone un elenco da utilizzare all'interno dell'assembly.

I commenti sono ben accetti su questo approccio.


@Samik - potresti approfondire, "in genere si consiglia di non esporre un oggetto List agli utenti esterni di un assembly, piuttosto di esporre un IEnumerable." perché è preferito ienumerable?
Howiecamp,

1
@Howiecamp: Principalmente perché IEnumerable fornisce un accesso di sola lettura all'elenco, dove come se esponessi direttamente l'elenco, può essere modificato dai client (come elementi di eliminazione ecc.), Che potrebbe non essere intenzionale.
Samik R,

Inoltre, esporre un Elenco o IList crea un contratto che consentirà sempre (ad esempio) un rapido accesso casuale ai dati. Se un giorno deciderai di cambiare il tuo metodo per utilizzare un'altra raccolta, o nessuna raccolta (rendimento), dovrai creare un Elenco solo per restituire un valore o interrompere il contratto modificando il tipo di ritorno del metodo. L'uso di un tipo di ritorno meno specifico ti offre flessibilità.

1

Tieni presente che qualsiasi classe definita come publicapparirà automaticamente nell'intellisense quando qualcuno guarda il tuo spazio dei nomi del progetto. Dal punto di vista dell'API, è importante mostrare agli utenti del progetto solo le classi che possono utilizzare. Usa la internalparola chiave per nascondere cose che non dovrebbero vedere.

Se il tuo Big_Important_Classprogetto A è destinato all'uso al di fuori del tuo progetto, non dovresti contrassegnarlo internal.

Tuttavia, in molti progetti, avrai spesso classi che sono destinate esclusivamente all'uso all'interno di un progetto. Ad esempio, potresti avere una classe che contiene gli argomenti in una chiamata al thread con parametri. In questi casi, dovresti contrassegnarli come internalse non altro per proteggerti da una modifica API indesiderata lungo la strada.


Quindi perché non usare privato?
Prigioniero ZERO

1
La privateparola chiave può essere utilizzata solo su classi o strutture definite all'interno di un'altra classe o struttura. Una classe definita all'interno di uno spazio dei nomi può solo essere dichiarata publico internal.
Matt Davis,

1

L'idea è che quando si progetta una libreria solo le classi che sono destinate all'uso dall'esterno (dai client della propria libreria) dovrebbero essere pubbliche. In questo modo puoi nascondere le lezioni

  1. È probabile che cambieranno nelle versioni future (se fossero pubbliche si romperà il codice client)
  2. Sono inutili per il cliente e possono causare confusione
  3. Non sono sicuri (quindi un uso improprio potrebbe danneggiare la tua libreria piuttosto male)

eccetera.

Se stai sviluppando soluzioni interne rispetto all'utilizzo di elementi interni non è così importante, immagino, perché di solito i clienti avranno un contatto costante con te e / o l'accesso al codice. Tuttavia sono abbastanza critici per gli sviluppatori di biblioteche.


-7

Quando hai classi o metodi che non si adattano perfettamente al paradigma orientato agli oggetti, che fanno cose pericolose, che devono essere chiamate da altre classi e metodi sotto il tuo controllo e che non vuoi far usare a nessun altro .

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

Cosa stai cercando di dire? Non è molto chiaro Al meno non a me.
pqsk,

Concordato con @pqsk
Anynomous Khan il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.