Che cosa si oppone all'uso di strutture per strutture dati passive?

7

Contesto

Recentemente ho letto della tecnica orientata agli oggetti per fare una distinzione tra oggetti e strutture dati passive , meglio riassunti in Clean Code:

"Objects hide their data [...] and expose functions [...]. Data structures expose their data and have no meaningful functions."

Sto pensando di utilizzare C # structs per strutture dati passive .
Per chiarire questo: SE una parte del mio codice deve funzionare come una struttura di dati passivi , quindi voglio usare struct per quello.

vantaggi

Fornirebbe una distinzione linguistica tra oggetti e strutture dati passive .

Inoltre, se un oggetto ha un campo privato di class , ma lo espone in una funzione, può essere cambiato da qualche altra parte. Ciò significa che i dati oggetto vengono modificati dall'esterno, il che non è positivo. So che dovresti ad es. esporre un List interno come ReadOnlyList , ma questa è buona pratica , che anche i buoni programmatori non sempre seguono. Utilizzando structs invece applica automaticamente questo

Quello che ho scoperto finora

Conosco la domanda " Quando utilizzare struct " ha già risposto più volte . Le risposte si riducono sempre al parere dei documenti ufficiali:

AVOID defining a struct unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (int, double, etc.).
  • It has an instance size under 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

Penso che i primi 2 punti siano per migliorare le prestazioni nello stack. Tuttavia, per quanto ho capito, structs è meglio nello stack, ma non è peggiore sull'heap.

Il punto 3 posso ancora aderire. Potrebbe rendere il codice più pulito, potrebbe renderlo più complicato, ma non lo so ancora.

Il punto 4 riguarda anche i miglioramenti delle prestazioni, ma in realtà non ho bisogno di molte prestazioni. Anche se, a questo punto, sarebbe presto, l'ottimizzazione iniziale: non sto lavorando con i big data qui.

Con un nome del genere, voglio pensare che structs sia esattamente la cosa da usare per le strutture di dati passivi orientate agli oggetti . La documentazione dei documenti ufficiali mi fa dubitare che, soprattutto, la limitazione delle dimensioni. Anche 2 stringhe per un indirizzo con 2 righe sarebbero già troppe.

La domanda

Esistono altri argomenti contro l'uso di strutture per queste strutture dati passive? O ho capito qualcosa di sbagliato?

Esempio

public struct EmployeeId // data structure (exposed data, no functions)
{
    public string Value;
}

public struct Address // data structure
{
    public string Line1;
    public string Line2;
}

public struct Performance // data structure
{
    public int Successes;
    public int Failures;
}

public struct Employee : IEquatable<Employee>
// data structure
{
    public EmployeeId Id:
    public Address Address;
    public Performance Performance;

    public bool Equals(Employee employee)
    {
        return Id == employee.Id;
    }
}

public class OfficialEmployeeRegistry // object (hidden data, exposed functions)
{
    private Dictionary<EmployeeId, Employee> _employees;

    public void Add(Employee employee)
    {
        _employees.Add(employee.Id, employee);
    }

    public List<Employee> GetPositivePerformers() {...}
}

public class SantaClause // object
{
    private EmployeeRegistry _employeeRegistry;
    private PresentSender _presentSender;

    public void SendChristmasPresents()
    {
        List<Employee> goodEmployees = employeeRegistry.GetPositivePerformers();
        foreach(Employee employee in goodEmployees)
        {
            _presentSender.SendPresent(employee.Address);
        }
    }
}

Tutti structs in questo codice sono esempi di ciò che voglio fare. Ad esempio, ora possiamo ottenere le prestazioni di un dipendente da OfficialEmployeeRegistry . Possiamo inviare tali dati a una classe di stampanti, ma se tale classe lo modifica nel processo, le voci in OfficialEmployeeRegistry sono protette. I dati ufficiali di OfficialEmployee saranno manipolati da solo. Oh, e le strutture dovrebbero essere immutabili, ovviamente, ma sento che aggiungere un costruttore a ognuno di loro farebbe ingrossare questo post.

Reazione agli impegni

Hai bisogno di serializzazione dei dati?
No.

Sarà necessario passare da e verso funzioni / metodi?
Sì.

Verrà iterato e modificato su base abbastanza significativa?
No. Immagino che questo riguardi le prestazioni; ma la performance non è sicuramente un problema

    
posta Raphael Schmitz 21.12.2017 - 23:33
fonte

7 risposte

2

What speaks against using structs for passive data structures?

Tecnicamente, niente.

(se puoi vivere con alcune restrizioni menzionate da Erik Eidt, come nessuna eredità).

Sono abbastanza sicuro che se si tenta di utilizzare le strutture C # per tutti gli oggetti dati, è comunque possibile scrivere programmi corretti seguendo questa convenzione. La limitazione della dimensione di 16 byte è solo un consiglio per le prestazioni e può essere tranquillamente ignorata.

Tuttavia, la vera domanda che dovresti porci è, questa convenzione produce programmi meglio mantenibili, evolutivi e leggibili, rispetto ai programmi che seguono la convenzione C # più usuale per usare le classi nella maggior parte dei casi, anche per la maggior parte dei DTO o "strutture dati passive" e strutture solo per casi eccezionali.

E questo è IMHO discutibile. Di solito preferisco utilizzare le classi prima di tutto nei miei programmi (anche per gli oggetti dati puri, quando ho solo bisogno di un elenco delle funzioni dei membri pubblici) e uso struct solo per tali ottimizzazioni come menzionato da @Ivan. Il motivo è che ho fatto l'esperienza che spesso quando inizio con una piccola struttura di dati in cui una semplice struttura sarebbe sufficiente, nel tempo i nuovi requisiti di questa struttura causano un po 'di evoluzioni in cui una classe completa inizia a dare più senso. E se ho usato quel struct in vari punti del programma, cambiarlo in una classe in seguito può introdurre molti errori minimi a causa del passaggio dalla semantica del valore alla semantica di riferimento.

Inoltre, se un tipo è definito come struct o come class non è immediatamente rilevabile durante la lettura del codice utilizzando quel tipo. Tuttavia, ci sono delle sottili differenze semantiche tra i due (vedi il commento di @BerinLoritsch sotto la domanda). L'utilizzo di struct solo per casi di ottimizzazione eccezionali rende gli errori nel mio codice causati da tali differenze meno probabili.

Quindi la mia raccomandazione qui è, usa struct s esclusivamente per strutture dati passive quando sai per certo che questa particolare struttura dati non evolverà mai in una classe, e solo se hai uno scenario di ottimizzazione utile. E se hai dubbi, meglio usare una classe.

    
risposta data 23.12.2017 - 11:50
fonte
10

Stai confondendo la parola chiave struct con il termine "struttura dati". Sono due cose molto diverse. Semplificando molto, un struct in C # è fondamentalmente un class con semantica del valore, mentre una struttura dati è una raccolta di dati con algoritmi di archiviazione e recupero che hanno specifiche caratteristiche di performance.

"Objects hide their data [...] and expose functions [...]. Data structures expose their data and have no meaningful functions."

Vista questa definizione, quello che stai cercando non è un struct , ma piuttosto un class con solo membri dati (nessuna funzione). struct s ha semantica del valore, che probabilmente non è ciò che vuoi mettere in una raccolta a meno che tu non stia veramente raccogliendo valori .

Di conseguenza, l'utilizzo della parola chiave struct per segnalare che stai raccogliendo dati senza alcun comportamento probabilmente non è un buon approccio. Gli sviluppatori di software competenti sanno già che le loro strutture di dati conterranno classi di oggetti; l'uso di una parola chiave della lingua che suggerisce il contrario sarà per loro fonte di confusione.

Utilizza struct per indica semantica del valore, per non indicare "oggetti solo dati".

Vedi anche
Oggetto di trasferimento dati
Struttura dati
Struttura passiva dei dati

    
risposta data 21.12.2017 - 23:49
fonte
5

Il concetto di struct C #, come vedi altrove, è un concetto di valore.

Quindi, un campo di tipo struct è un valore, non un riferimento. Un campo di tipo struct è un valore incorporato.

Questo non è particolarmente adatto ai tipi di dati ricorsivi , come ad esempio alberi ed elenchi. Questi tipi di dati ricorsivi richiedono più entità che possono fare riferimento l'un l'altro.

Per ottenere più oggetti e riferimenti, è necessario inserire le strutture in modo che diventino oggetti. Questo può essere fatto in diversi modi. Uno è quello di utilizzare le interfacce, ma ora si stanno implementando i metodi (anche se per lo più solo getter). Un altro è usare i puntatori unsafe . E l'ennesimo è l'upcast, che perde informazioni di tipo prezioso (in fase di compilazione). Nessuno di questi è attraente rispetto alla semplicità delle classi con campi pubblici.

Anche le strutture C # non giocano bene con l'ereditarietà e il polimorfismo (o con le unioni taggate). Non possiamo usare una struttura come base per un'altra. Anche se possiamo incorporarne uno in un altro, ciò è anche imbarazzante quando volevamo il polimorfismo, che potremmo voler usare in alcuni tipi di dati ricorsivi.

Le classi possono essere rese immutabili, supportano il polimorfismo e i riferimenti, quindi funzionano piuttosto bene per le strutture dati, specialmente per i tipi di dati ricorsivi.

    
risposta data 22.12.2017 - 01:44
fonte
2

È incredibile come la semplice modifica della rotazione di un'istruzione cambierà il suo sentimento ...

Scegli una struct invece di una classe if:

  • Rappresenta logicamente un singolo valore, simile ai tipi primitivi (int, double, ecc.).
  • Ha una dimensione dell'istanza inferiore a 16 byte.
  • È immutabile.
  • Non dovrà essere inserito frequentemente in riquadri.

Altri punti da considerare:

  • Tutti i campi di una struct devono essere strutturati o tipi primitivi.
  • Se l'identità non ha molto senso per il tipo di dati, quindi crea una struttura. (I.E. se due oggetti uguali sono essenzialmente indistinguibili).

Per essere specifici e diretti:

Are there other arguments against using structs for these data structures? Or did I understand something wrong?

Non ci sono argomenti contro l'uso di struct per strutture di dati che soddisfano le condizioni di cui sopra.

Per quanto riguarda i tipi che non abbastanza li soddisfano ...

Se il tuo tipo deve essere inserito frequentemente, allora il tipo probabilmente non rappresenta un valore.

Se il tuo tipo non è immutabile, allora non si comporta come un valore.

Se la dimensione dell'istanza è superiore a 16 byte ma soddisfa i requisiti dell'essere una struct, quindi il tipo mantiene un oggetto heap internamente in modo che sia 16 byte.

Se non è logicamente un singolo valore, allora non dovrebbe essere una struttura.

    
risposta data 22.12.2017 - 00:21
fonte
0

La più grande distinzione di comportamento tra struct e class è che un struct è passato per valore mentre un class è passato per riferimento. Ci sono molte sorprese in cui puoi imbatterti in quel look corretto, ma non comportarti correttamente - perché il codice è stato scritto supponendo che funzionasse con una class . Le sorprese fanno male alle persone che devono mantenere il software.

Ciò significa che il seguente codice può comportarsi in modo molto diverso a seconda che Message sia una struct o una classe:

public static void CreateTitle(Message message)
{
    message.Title = "Created the Title";
}

public static void Main()
{
    Message m = new Message
    {
        Title = "Unset",
        Content = "Message content"
    };

    CreateTitle(m);

    Console.WriteLine($"> {m.Title}: {m.Message}");
}

Se Message è una classe, vedrai:

> Created the Title: Message content

Se Message è una struttura vedrai:

> Unset: Message content

Questo è effettivamente desiderabile quando si utilizza un struct per passare lo stato di avanzamento attraverso un evento. Essenzialmente il tuo codice di elaborazione può continuare a contare e un'istantanea di struct viene copiata nel gestore di eventi.

Tuttavia, se hai a che fare con la gestione generale dei dati, avrai probabilmente bisogno della semantica di class . I motivi dell'elenco di avvertimenti da parte di Microsoft è quello di costringerti a pensare alle implicazioni. Nella maggior parte dei casi vorrai il comportamento di class .

In effetti, puoi fare in modo che il metodo CreateTitle() si comporti allo stesso modo indipendentemente dal fatto che Message sia un struct o un class se dici esplicitamente di passare per riferimento:

public static void CreateTitle(ref Message message) { /* .... */ }

Dichiarazione di non responsabilità: questo è un esempio forzato per mostrare le implicazioni pratiche dei tipi di sorprese da aspettarsi quando si utilizza struct anziché class . In realtà non scrivo codice in questo modo.

    
risposta data 22.12.2017 - 18:39
fonte
0

Concettualmente, la differenza tra una classe e una struttura è che una struttura è una caratteristica più basilare, non orientata agli oggetti.

Internamente, le classi sono implementate come un modello di programmazione costruito su struct - il comportamento e le funzionalità aggiuntivi che le classi possono essere completamente reimplementate in termini di strutture e codice procedurale.

La distinzione viene mantenuta (cioè le strutture rimangono nei linguaggi moderni) principalmente per ragioni di prestazioni da un lato (le classi consumano più memoria e hanno overheads nell'esecuzione, e sono immagazzinate di default sull'heap invece dello stack), e motivi di interoperabilità sull'altro (cioè per interagire con il codice esterno che viene scritto e chiamato in modo non orientato agli oggetti - a causa della sua età o per motivi di prestazioni a sua volta).

Quando dico performance, per essere chiari nella maggior parte dei casi sto parlando di un paio di byte in più di memoria, o di qualche istruzione in più per chiamata di metodo - una grave inefficienza 40 anni fa, ma non spesso al giorno d'oggi (eccetto per alcune funzioni del sistema operativo o algoritmi specifici). Gli stessi argomenti erano presenti sull'uso delle istruzioni GOTO (cioè l'argomento tra le caratteristiche del linguaggio strutturato e non strutturato).

La differenza sottostante è anche sfocata in molte lingue di uso generale.

Ad esempio, alcune funzionalità orientate agli oggetti, come i metodi dei membri, possono essere aggiunte alle strutture senza alcun sovraccarico aggiuntivo, ma tali membri non sono implementati come puntatori di funzione memorizzati all'interno della struct, così come sono nelle classi.

È lo spazio di archiviazione e riscrittura di puntatori di funzione memorizzati internamente, che consente ai metodi dei membri delle classi di essere: (a) sovrascritti senza riscrivere (o ri-implementare) il codice della classe base, e (b) chiamato direttamente dal codice esterno, in un modo che i metodi dei membri sulle strutture non possono essere.

Nella maggior parte dei casi al giorno d'oggi, qualsiasi uso delle strutture in cui si svolgerà una classe, è un'ottimizzazione prematura e non necessaria.

    
risposta data 30.12.2017 - 05:45
fonte
-1

Le classi non riguardano solo il recupero. Possono anche essere utilizzati per applicare contratti e invarianti sui dati: ad esempio, il loro indirizzo è valido. Se non stai cercando invarianti sui tuoi dati allora vai avanti, usa una struttura. Altrimenti, considera l'utilizzo di una classe.

    
risposta data 23.12.2017 - 05:51
fonte

Leggi altre domande sui tag