Quali insidie sono insite nell'uso di classi parziali?

4

Oltre a questa domanda nei commenti che ho citato ho sentito che le classi parziali sono meglio evitate se possibile.

E se qualcuna fosse la ragione di questo sentimento? O se questo è un sentimento non valido, come vengono superati i detrimenti percepiti?

    
posta Bob 24.07.2012 - 10:49
fonte

6 risposte

7

Oltre al codice generato dal designer, le classi parziali possono ancora essere utili senza compromettere la qualità del codice.

Esempi di uso corretto

Alcuni utilizzi che ho trovato particolarmente utili sono i seguenti:

  1. query SQL

    Faccio un sacco di cose in cui vengono usate query SQL dirette e scritte a mano invece di ORM. L'architettura più semplice è incorporare le query direttamente nel codice:

    public class Demo
    {
        [...]
    
        public int GetPriceOfProduct(long productId)
        {
            var query = Query(
                "select top 1 [Price] from [Shop].[Products] where [ProductId] = @ProductId",
                this.ConnectionString);
    
            // Use the query to retrieve the price.
        }
    
        [...]
    }
    

    Funziona bene, finché non ci sono troppi metodi e fino a quando troppe query iniziano ad avere più di dieci righe. Anche la revisione delle query da parte del DBA è complicata, dal momento che il DBA deve cercare attraverso tutto il codice. In questo caso, le query possono essere inserite in una classe separata:

    public class Demo
    {
        [...]
    
        public int GetPriceOfProduct(long productId)
        {
            var query = Query(Queries.PriceOfProduct, this.ConnectionString);
    
            // Use the query to retrieve the price.
        }
    
        [...]
    
        private static class Queries
        {
            public const string PriceOfProduct =
                @"select top 1 [Price] from [Shop].[Products] where [ProductId] = @ProductId";
        }
    }
    

    Una volta terminato, la sottoclasse Queries è un buon candidato da inserire in un file separato.

    Un modo è renderlo internal , rinominare DemoQueries e creare un file DemoQueries.cs . Il problema è che il codice sarà più lungo (specificare ProductOfUserRelationQueries invece di solo Queries è in realtà un po 'più lungo) e che la classe inquinerà l'assembly (incluso Intellisense), mentre è necessario accedervi solo in Demo class.

    Un altro modo è di rendere Demo partial e di avere le query in un file separato, mantenendo la classe Queries in Demo .

  2. Nomi nella cache

    Quando metti in cache alcuni elementi, è importante essere coerenti con i nomi (chiavi) usati nella cache. Per questo e simile al primo esempio, si può finire con una classe privata contenente un sacco di metodi come questo:

    public class Demo
    {
        [...]
    
        private static class CacheNames
        {
            public static string PriceOfProduct(long productId)
            {
                return string.Format(@"Price(ProductId<{0}>)", productId);
            }
        }
    
        [...]
    }
    
  3. traccia

    Lo stesso vale per la traccia. A volte, un codice che si basa molto sulla traccia è quasi illeggibile dal momento che gli ID ei messaggi di traccia sprecano troppo spazio. Mettere una this.Trace(TraceId.UserRemoved) rende il codice molto più breve, ma devi definire tutti i TraceId s e i relativi ID evento e messaggi da qualche parte.

  4. sovraccarichi

    Questo caso è piuttosto strano e deve essere evitato nella maggior parte dei casi, ma comunque ci sono esempi validi in cui le classi parziali aiutano.

    Ho avuto un progetto in cui i metodi avevano un'enorme quantità di overload e altri metodi che non stavano facendo nulla di utile piuttosto che chiamare un metodo diverso cambiando alcuni parametri. Erano richiesti perché erano utilizzati dai consumatori della biblioteca, ma era davvero fastidioso vederli tutti nel codice sorgente e essere distratti dal codice reale (notare che anche un sovraccarico può essere piuttosto lungo in termini di LOC: a almeno tre righe per XMLDoc, ma a volte erano più di venti righe, poi il metodo stesso, con i suoi contratti di codice e poi una piccola chiamata allo stesso metodo con parametri diversi).

    Mettendo in un file separato tutti i sovraccarichi e i metodi che non stavano facendo troppo aiutarono a concentrarsi sul codice base.

Le classi parziali sono le stesse delle regioni

#region s non è buona , e uno dei motivi per cui sono così cattivi è che danno hai l'impressione che il tuo codice sia piccolo, quando in realtà ha migliaia di righe ed è completamente illeggibile. Il problema principale è quando qualcuno li usa all'interno di un metodo, per finire con centinaia di linee di codice in questo metodo che ora fa venti cose invece di una.

Le classi parziali hanno lo stesso problema: se ti affidi troppo a loro, puoi avere l'impressione che la tua classe sia piuttosto piccola, mentre dovrebbe essere stata sottoposta a refactoring per mesi.

Potrebbero rendere difficile la navigazione del codice

Un altro difetto con le classi parziali è che non è così facile sapere dove sono le cose. Prendi Initialize() in Windows Form. Per una persona che scopre Windows Form e classi parziali, non è così facile capire dove si trova questo metodo: è un metodo della classe genitore Form e fa parte di .NET Framework? O forse è da qualche parte nel codice per MyForm ? Oh, no, è in un codice generato dal designer.

Sì, Visual Studio ha la funzione F12 - Vai alla definizione, ma se puoi organizzare il tuo codice in file in modo che la persona possa sapere dove si trova un metodo, fallo.

    
risposta data 24.07.2012 - 13:20
fonte
7

Una delle insidie è pensare che le classi parziali siano in qualche modo legate alla modularità del progetto. Questa situazione è un esempio perfetto.

Le classi parziali sono valide solo se hai parti su una singola classe create con mezzi completamente diversi. L'uso comune è di avere un pezzo di classe generato da qualche strumento (progettista dell'interfaccia utente, generatore di classi del modello, generatore di servizi) e un'altra parte aggiunta dallo sviluppatore. La parte generata dagli strumenti può essere rigenerata in qualsiasi momento. Non così con la parte aggiunta dallo sviluppatore.

    
risposta data 24.07.2012 - 11:19
fonte
0

C'è una situazione che ho dovuto affrontare un paio di volte prima.

Sebbene le classi parziali consentano il concetto di metodi parziali, non consentono costruttori parziali. Quindi, se stai usando il codice generato dalla macchina, molto probabilmente ci sarà già un costruttore definito che significa che non puoi aggiungere codice di costruzione personalizzato.

Un approccio comune a questo è ovviamente chiamare altri "metodi di costruzione" direttamente dal costruttore definito come avere

// example constructor of the partial class
public PartialClass {

    partial void Initialize1();
    partial void Initialize2();
    partial void Initialize3();
    // ...
}

ma ciò richiede l'uso esplicito di un tale schema che, ovviamente, non è il caso quando si lavora con codice straniero o generato da una macchina.

    
risposta data 24.07.2012 - 12:50
fonte
0

Le classi parziali sono un must in .Net nella mia esperienza. Ad esempio, una finestra in WPF inizia come una classe parziale poiché molta dell'implementazione è la piastra della caldaia che viene generata automaticamente per te.

Un altro caso è quando si utilizza Entity Framework o altri generatori di codice. Creerà oggetti che rappresentano oggetti nel database. È possibile aggiungere le modifiche da apportare, ad esempio aggiungendo proprietà calcolate alla classe nel file di codice generato, ma ciò non sarebbe l'ideale, poiché ogni volta che si modifica il modello, il generatore di codice eliminerebbe le funzionalità personalizzate. Quindi dovresti aggiungere una classe parziale al di fuori del codice generato che la estende per aggiungere le funzionalità che desideri.

Nessuna regola assoluta può essere fatta (anche questa :-)) sulle migliori pratiche perché i problemi da risolvere possono essere così diversi. Esempio: non usare GOTOs eccetto che al livello più basso il codice del compilatore / assemblatore ha GOTO in tutto il posto da implementare per loop e ifs.

Sostituirei la regola no partial con: se le cose sono così complicate pensate di aver bisogno di spostare parte del codice in un altro file, riflettete se è necessario suddividere le cose in classi più piccole. Se una soluzione di classe più piccola non ha senso impazzire con i partial.

    
risposta data 25.07.2012 - 20:53
fonte
0

Un pericolo non ancora citato con classi parziali è che non c'è modo, a meno di aggiungere un ulteriore livello di ereditarietà, di indicare che tutta la costruzione di un oggetto deve essere fatta attraverso un particolare costruttore. Dato:

class Fnorble
{
  protected readonly int[] biz,boz;
  public Fnorble(int size)
  {
    biz = new int[size];
    boz = new int[size];
  }
}

Si potrebbe sperare che si possa assumere come invarianza che biz e boz saranno sempre matrici di dimensioni identiche, ma a meno che non si effettui una ricerca nell'intero progetto non si può essere sicuri. Se un altro file contiene una definizione:

partial class Fnorble
{
  public Fnorble(int bizSize, bozSize)
  {
    biz = new int[bizSize];
    boz = new int[bozSize];
  }
}

quindi l'invariante precedentemente menzionato non reggerebbe.

    
risposta data 02.02.2015 - 20:53
fonte
0

IMHO c'è solo un caso in cui l'uso di classi parziali ha senso. Ma in questo caso ha molto senso.

E quel caso è quando si utilizza uno strumento per generare automaticamente il codice, ad es. basato su uno schema di database, file di schema XSD o altro, ed è necessario aggiungere funzioni alle classi di codice generate automaticamente.

In questo caso, le classi parziali sono un ottimo strumento per permetterti di scrivere codice personalizzato che non viene sovrascritto la volta successiva che lo strumento di generazione del codice viene eseguito. Ho avuto un esempio in cui abbiamo aggiunto alcune interfacce alle classi generate da uno schema XSD per evitare la duplicazione nel codice che elabora i dati XML corrispondenti.

Ma stai scrivendo tutto il codice a mano, suddividere il codice in classi parziali è un odore di codice, un'indicazione che la tua classe sta diventando troppo grande / ha troppe responsabilità.

    
risposta data 02.02.2015 - 21:18
fonte

Leggi altre domande sui tag