Linee guida per la codifica - MRTK2

Questo documento illustra i principi e le convenzioni di codifica da seguire quando si contribuisce a MRTK.


Filosofia

Essere concisi e sforzarsi di semplicità

La soluzione più semplice è spesso la migliore. Si tratta di un obiettivo prioritario di queste linee guida e deve essere l'obiettivo di tutte le attività di codifica. La parte di essere semplice è concisa e coerente con il codice esistente. Provare a mantenere il codice semplice.

I lettori devono incontrare solo artefatti che forniscono informazioni utili. Ad esempio, i commenti che riformulare ciò che è ovvio non forniscono informazioni aggiuntive e aumentano il rapporto rumore-segnale.

Mantenere semplice la logica del codice. Si noti che non si tratta di un'istruzione relativa all'uso del minor numero di righe, riducendo al minimo le dimensioni dei nomi degli identificatori o dello stile di parentesi graffa, ma riducendo il numero di concetti e ottimizzando la visibilità di tali righe tramite modelli familiari.

Produrre codice coerente e leggibile

La leggibilità del codice è correlata a basse percentuali di difetto. Cercare di creare codice facile da leggere. Cercare di creare codice con logica semplice e riutilizzare i componenti esistenti, perché consente anche di garantire la correttezza.

Tutti i dettagli del codice che si produce, dal dettaglio più semplice della correttezza allo stile e alla formattazione coerenti. Mantenere lo stile di codifica coerente con ciò che esiste già, anche se non corrisponde alla preferenza. Ciò aumenta la leggibilità della codebase complessiva.

Supporto della configurazione dei componenti sia nell'editor che in fase di esecuzione

MRTK supporta un set diversificato di utenti: utenti che preferiscono configurare componenti nell'editor di Unity e caricare prefab e utenti che devono creare un'istanza e configurare oggetti in fase di esecuzione.

Tutto il codice deve funzionare aggiungendo un componente a un GameObject in una scena salvata e creando un'istanza di tale componente nel codice. I test devono includere un test case per la creazione di istanze dei prefab e la creazione di istanze, la configurazione del componente in fase di esecuzione.

L'editor di riproduzione è la prima piattaforma di destinazione e primaria

Play-In-Editor è il modo più rapido per eseguire l'iterazione in Unity. Fornire ai nostri clienti modi per eseguire rapidamente l'iterazione consente loro di sviluppare soluzioni più rapidamente e provare più idee. In altre parole, massimizzare la velocità di iterazione consente ai nostri clienti di ottenere di più.

Rendere tutto funzionante nell'editor, quindi renderlo funzionante su qualsiasi altra piattaforma. Continuare a lavorare nell'editor. È facile aggiungere una nuova piattaforma a Play-In-Editor. È molto difficile usare Play-In-Editor se l'app funziona solo su un dispositivo.

Aggiungere nuovi campi pubblici, proprietà, metodi e campi privati serializzati con attenzione

Ogni volta che si aggiunge un metodo pubblico, un campo, una proprietà, diventa parte della superficie API pubblica di MRTK. I campi privati contrassegnati con [SerializeField] espongono anche i campi all'editor e fanno parte della superficie API pubblica. Altri utenti potrebbero usare tale metodo pubblico, configurare prefab personalizzati con il campo pubblico e prendere una dipendenza da esso.

I nuovi membri pubblici devono essere esaminati attentamente. Qualsiasi campo pubblico dovrà essere mantenuto in futuro. Tenere presente che se il tipo di un campo pubblico (o un campo privato serializzato) cambia o viene rimosso da un MonoBehaviour, che potrebbe interrompere altre persone. Il campo dovrà prima essere deprecato per una versione e il codice per eseguire la migrazione delle modifiche per gli utenti che hanno acquisito dipendenze dovrà essere fornito.

Classificare in ordine di priorità la scrittura di test

MRTK è un progetto della community, modificato da un'ampia gamma di collaboratori. Questi collaboratori potrebbero non conoscere i dettagli della correzione di bug/funzionalità e interrompere accidentalmente la funzionalità. MRTK esegue test di integrazione continua prima di completare ogni richiesta pull. Le modifiche che interrompono i test non possono essere archiviate. Pertanto, i test sono il modo migliore per garantire che altre persone non interrompano la funzionalità.

Quando si corregge un bug, scrivere un test per assicurarsi che non regredisca in futuro. Se si aggiunge una funzionalità, scrivere test che verificano il funzionamento della funzionalità. Questa operazione è necessaria per tutte le funzionalità dell'esperienza utente, ad eccezione delle funzionalità sperimentali.

Convenzioni di codifica C#

Intestazioni delle informazioni sulle licenze script

Tutti i dipendenti Microsoft che contribuiscono a nuovi file devono aggiungere l'intestazione di licenza standard seguente all'inizio dei nuovi file, esattamente come illustrato di seguito:

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

Intestazioni di riepilogo di funzione/metodo

Tutte le classi pubbliche, gli struct, le enumerazioni, le funzioni, le proprietà, i campi inseriti in MRTK devono essere descritti come scopo e uso, esattamente come illustrato di seguito:

/// <summary>
/// The Controller definition defines the Controller as defined by the SDK / Unity.
/// </summary>
public struct Controller
{
    /// <summary>
    /// The ID assigned to the Controller
    /// </summary>
    public string ID;
}

In questo modo, la documentazione viene generata e distribuita correttamente per tutte le classi, i metodi e le proprietà.

Tutti i file di script inviati senza tag di riepilogo appropriati verranno rifiutati.

Regole dello spazio dei nomi MRTK

Il toolkit Realtà mista usa un modello di spazio dei nomi basato su funzionalità, in cui tutti gli spazi dei nomi fondamentali iniziano con "Microsoft.MixedReality.Toolkit". In generale, non è necessario specificare il livello toolkit (ad esempio Core, Providers, Services) negli spazi dei nomi.

Gli spazi dei nomi attualmente definiti sono:

  • Microsoft.MixedReality.Toolkit
  • Microsoft.MixedReality.Toolkit.Boundary
  • Microsoft.MixedReality.Toolkit.Diagnostics
  • Microsoft.MixedReality.Toolkit.Editor
  • Microsoft.MixedReality.Toolkit.Input
  • Microsoft.MixedReality.Toolkit.SpatialAwareness
  • Microsoft.MixedReality.Toolkit.Teleport
  • Microsoft.MixedReality.Toolkit.Utilities

Per gli spazi dei nomi con una grande quantità di tipi, è accettabile creare un numero limitato di sotto-spazi dei nomi per facilitare l'utilizzo dell'ambito.

Se si omette lo spazio dei nomi per un'interfaccia, una classe o un tipo di dati, la modifica verrà bloccata.

Aggiunta di nuovi script MonoBehaviour

Quando si aggiungono nuovi script MonoBehaviour con una richiesta pull, assicurarsi che l'attributo AddComponentMenu venga applicato a tutti i file applicabili. Ciò garantisce che il componente sia facilmente individuabile nell'editor sotto il pulsante Aggiungi componente . Il flag di attributo non è necessario se il componente non può essere visualizzato nell'editor, ad esempio una classe astratta.

Nell'esempio seguente, il pacchetto qui deve essere compilato con il percorso del pacchetto del componente. Se si inserisce un elemento nella cartella MRTK/SDK , il pacchetto sarà SDK.

[AddComponentMenu("Scripts/MRTK/{Package here}/MyNewComponent")]
public class MyNewComponent : MonoBehaviour

Aggiunta di nuovi script di controllo unity

In generale, provare a evitare di creare script di controllo personalizzati per i componenti MRTK. Aggiunge un sovraccarico aggiuntivo e la gestione della codebase che può essere gestita dal motore unity.

Se è necessaria una classe inspector, provare a usare DrawDefaultInspector(). Questo semplifica di nuovo la classe inspector e lascia gran parte del lavoro a Unity.

public override void OnInspectorGUI()
{
    // Do some custom calculations or checks
    // ....
    DrawDefaultInspector();
}

Se il rendering personalizzato è necessario nella classe inspector, provare a usare SerializedProperty e EditorGUILayout.PropertyField. In questo modo Unity gestisce correttamente il rendering dei prefab annidati e dei valori modificati.

Se EditorGUILayout.PropertyField non è possibile usare a causa di un requisito nella logica personalizzata, assicurarsi che tutto l'utilizzo venga racchiuso in un oggetto EditorGUI.PropertyScope. In questo modo Unity eseguirà correttamente il rendering del controllo per i prefab annidati e i valori modificati con la proprietà specificata.

Inoltre, provare a decorare la classe di controllo personalizzato con un .CanEditMultipleObjects Questo tag garantisce che sia possibile selezionare e modificare più oggetti con questo componente nella scena. Tutte le nuove classi di controllo devono verificare che il codice funzioni in questa situazione nella scena.

    // Example inspector class demonstrating usage of SerializedProperty & EditorGUILayout.PropertyField
    // as well as use of EditorGUI.PropertyScope for custom property logic
    [CustomEditor(typeof(MyComponent))]
    public class MyComponentInspector : UnityEditor.Editor
    {
        private SerializedProperty myProperty;
        private SerializedProperty handedness;

        protected virtual void OnEnable()
        {
            myProperty = serializedObject.FindProperty("myProperty");
            handedness = serializedObject.FindProperty("handedness");
        }

        public override void OnInspectorGUI()
        {
            EditorGUILayout.PropertyField(destroyOnSourceLost);

            Rect position = EditorGUILayout.GetControlRect();
            var label = new GUIContent(handedness.displayName);
            using (new EditorGUI.PropertyScope(position, label, handedness))
            {
                var currentHandedness = (Handedness)handedness.enumValueIndex;

                handedness.enumValueIndex = (int)(Handedness)EditorGUI.EnumPopup(
                    position,
                    label,
                    currentHandedness,
                    (value) => {
                        // This function is executed by Unity to determine if a possible enum value
                        // is valid for selection in the editor view
                        // In this case, only Handedness.Left and Handedness.Right can be selected
                        return (Handedness)value == Handedness.Left
                        || (Handedness)value == Handedness.Right;
                    });
            }
        }
    }

Aggiunta di nuovi ScriptableObject

Quando si aggiungono nuovi script ScriptableObject, assicurarsi che l'attributo CreateAssetMenu venga applicato a tutti i file applicabili. Ciò garantisce che il componente sia facilmente individuabile nell'editor tramite i menu di creazione degli asset. Il flag di attributo non è necessario se il componente non può essere visualizzato nell'editor, ad esempio una classe astratta.

Nell'esempio seguente, la sottocartella deve essere riempita con la sottocartella MRTK, se applicabile. Se si inserisce un elemento nella cartella MRTK/Providers , il pacchetto sarà Provider. Se si inserisce un elemento nella cartella MRTK/Core , impostarlo su "Profili".

Nell'esempio seguente l' | MyNewService MyNewProvider deve essere compilato con il nome della nuova classe, se applicabile. Se si inserisce un elemento nella cartella MixedRealityToolkit , lasciare questa stringa.

[CreateAssetMenu(fileName = "MyNewProfile", menuName = "Mixed Reality Toolkit/{Subfolder}/{MyNewService | MyNewProvider}/MyNewProfile")]
public class MyNewProfile : ScriptableObject

Registrazione

Quando si aggiungono nuove funzionalità o si aggiornano le funzionalità esistenti, è consigliabile aggiungere i log DebugUtilities.LogVerbose al codice interessante che potrebbe essere utile per il debug futuro. C'è un compromesso qui tra l'aggiunta della registrazione e il rumore aggiunto e la registrazione non sufficiente (che rende difficile la diagnosi).

Un esempio interessante in cui la registrazione è utile (insieme al payload interessante):

DebugUtilities.LogVerboseFormat("RaiseSourceDetected: Source ID: {0}, Source Type: {1}", source.SourceId, source.SourceType);

Questo tipo di registrazione può aiutare a rilevare problemi come https://github.com/microsoft/MixedRealityToolkit-Unity/issues/8016, che sono stati causati da eventi di origine non corrispondenti rilevati e di origine persi.

Evitare di aggiungere log per i dati e gli eventi che si verificano in ogni frame, in modo ideale la registrazione deve coprire gli eventi "interessanti" basati su input utente distinti (ad esempio un "clic" da un utente e il set di modifiche ed eventi provenienti da che sono interessanti per registrare). Lo stato in corso di "utente sta ancora tenendo un movimento" registrato ogni frame non è interessante e soprafficherà i log.

Si noti che questa registrazione dettagliata non è attivata per impostazione predefinita (deve essere abilitata nelle impostazioni di sistema di diagnostica)

Spazi e schede

Assicurarsi di usare 4 spazi anziché schede quando si contribuisce a questo progetto.

Spaziatura

Non aggiungere spazi aggiuntivi tra parentesi quadre e parentesi:

Cosa non fare

private Foo()
{
    int[ ] var = new int [ 9 ];
    Vector2 vector = new Vector2 ( 0f, 10f );
}

Cosa fare

private Foo()
{
    int[] var = new int[9];
    Vector2 vector = new Vector2(0f, 10f);
}

Convenzioni di denominazione

Usare PascalCase sempre per le proprietà. Usare camelCase per la maggior parte dei campi, ad eccezione dell'uso PascalCase per static readonly e const dei campi. L'unica eccezione a questa è per le strutture di dati che richiedono che i campi vengano serializzati da JsonUtility.

Cosa non fare

public string myProperty; // <- Starts with a lowercase letter
private string MyField; // <- Starts with an uppercase letter

Cosa fare

public string MyProperty;
protected string MyProperty;
private static readonly string MyField;
private string myField;

Modificatori di accesso

Dichiara sempre un modificatore di accesso per tutti i campi, le proprietà e i metodi.

  • Tutti i metodi API Unity devono essere private per impostazione predefinita, a meno che non sia necessario eseguirne l'override in una classe derivata. In questo caso protected deve essere usato.

  • I campi devono essere privatesempre , con public o protected funzioni di accesso alle proprietà.

  • Usare i membri con corpo dell'espressione e le proprietà automatiche , se possibile

Cosa non fare

// protected field should be private
protected int myVariable = 0;

// property should have protected setter
public int MyVariable => myVariable;

// No public / private access modifiers
void Foo() { }
void Bar() { }

Cosa fare

public int MyVariable { get; protected set; } = 0;

private void Foo() { }
public void Bar() { }
protected virtual void FooBar() { }

Usare parentesi graffe

Usare sempre le parentesi graffe dopo ogni blocco di istruzioni e inserirle sulla riga successiva.

Cosa non fare

private Foo()
{
    if (Bar==null) // <- missing braces surrounding if action
        DoThing();
    else
        DoTheOtherThing();
}

Cosa non fare

private Foo() { // <- Open bracket on same line
    if (Bar==null) DoThing(); <- if action on same line with no surrounding brackets
    else DoTheOtherThing();
}

Cosa fare

private Foo()
{
    if (Bar==true)
    {
        DoThing();
    }
    else
    {
        DoTheOtherThing();
    }
}

Le classi pubbliche, gli struct e le enumerazioni devono andare tutti nei propri file

Se la classe, lo struct o l'enumerazione possono essere resi privati, è consigliabile includere nello stesso file. Ciò consente di evitare problemi di compilazione con Unity e assicurarsi che si verifichi un'astrazione corretta del codice, riducendo anche i conflitti e le modifiche di rilievo quando il codice deve cambiare.

Cosa non fare

public class MyClass
{
    public struct MyStruct() { }
    public enum MyEnumType() { }
    public class MyNestedClass() { }
}

Cosa fare

 // Private references for use inside the class only
public class MyClass
{
    private struct MyStruct() { }
    private enum MyEnumType() { }
    private class MyNestedClass() { }
}

Cosa fare

MyStruct.cs

// Public Struct / Enum definitions for use in your class.  Try to make them generic for reuse.
public struct MyStruct
{
    public string Var1;
    public string Var2;
}

MyEnumType.cs

public enum MuEnumType
{
    Value1,
    Value2 // <- note, no "," on last value to denote end of list.
}

MyClass.cs

public class MyClass
{
    private MyStruct myStructReference;
    private MyEnumType myEnumReference;
}

Inizializzare le enumerazioni

Per assicurarsi che tutte le enumerazioni vengano inizializzate correttamente a partire da 0, .NET offre un collegamento ordinato per inizializzare automaticamente l'enumerazione aggiungendo semplicemente il primo valore (iniziale). (ad esempio Valore 1 = 0 Valori rimanenti non sono necessari)

Cosa non fare

public enum Value
{
    Value1, <- no initializer
    Value2,
    Value3
}

Cosa fare

public enum ValueType
{
    Value1 = 0,
    Value2,
    Value3
}

Ordina enumerazioni per l'estensione appropriata

È fondamentale che se un Enum è probabilmente esteso in futuro, per ordinare le impostazioni predefinite nella parte superiore dell'enumerazione, ciò garantisce che gli indici Enum non siano interessati da nuove aggiunte.

Cosa non fare

public enum SDKType
{
    WindowsMR,
    OpenVR,
    OpenXR,
    None, <- default value not at start
    Other <- anonymous value left to end of enum
}

Cosa fare

/// <summary>
/// The SDKType lists the VR SDKs that are supported by the MRTK
/// Initially, this lists proposed SDKs, not all may be implemented at this time (please see ReleaseNotes for more details)
/// </summary>
public enum SDKType
{
    /// <summary>
    /// No specified type or Standalone / non-VR type
    /// </summary>
    None = 0,
    /// <summary>
    /// Undefined SDK.
    /// </summary>
    Other,
    /// <summary>
    /// The Windows 10 Mixed reality SDK provided by the Universal Windows Platform (UWP), for Immersive MR headsets and HoloLens.
    /// </summary>
    WindowsMR,
    /// <summary>
    /// The OpenVR platform provided by Unity (does not support the downloadable SteamVR SDK).
    /// </summary>
    OpenVR,
    /// <summary>
    /// The OpenXR platform. SDK to be determined once released.
    /// </summary>
    OpenXR
}

Esaminare l'uso dell'enumerazione per i campi di bit

Se è possibile che un'enumerazione richieda più stati come valore, ad esempio La mano = Sinistra & destra. Quindi l'Enumerazione deve essere decorata correttamente con BitFlags per abilitarla per usarla correttamente

Il file Handedness.cs ha un'implementazione concreta per questo

Cosa non fare

public enum Handedness
{
    None,
    Left,
    Right
}

Cosa fare

[Flags]
public enum Handedness
{
    None = 0 << 0,
    Left = 1 << 0,
    Right = 1 << 1,
    Both = Left | Right
}

Percorsi di file con codice rigido

Quando si generano percorsi di file stringa e in particolare si scrivono percorsi di stringa hardcoded, eseguire le operazioni seguenti:

  1. Usare le API diPath C#ogni volta che è possibile, ad esempio Path.Combine o Path.GetFullPath.
  2. Usare / o Path.DirectorySeparatorChar invece di \ o \\.

Questi passaggi garantiscono che MRTK funzioni sia nei sistemi basati su Windows che su Unix.

Cosa non fare

private const string FilePath = "MyPath\\to\\a\\file.txt";
private const string OtherFilePath = "MyPath\to\a\file.txt";

string filePath = myVarRootPath + myRelativePath;

Cosa fare

private const string FilePath = "MyPath/to/a/file.txt";
private const string OtherFilePath = "folder{Path.DirectorySeparatorChar}file.txt";

string filePath = Path.Combine(myVarRootPath,myRelativePath);

// Path.GetFullPath() will return the full length path of provided with correct system directory separators
string cleanedFilePath = Path.GetFullPath(unknownSourceFilePath);

Procedure consigliate, incluse le raccomandazioni di Unity

Alcune delle piattaforme di destinazione di questo progetto richiedono di prendere in considerazione le prestazioni. Con questa attenzione, prestare sempre attenzione quando si alloca la memoria in un codice chiamato spesso in cicli di aggiornamento stretti o algoritmi.

Incapsulamento

Usare sempre campi privati e proprietà pubbliche se è necessario accedere al campo dall'esterno della classe o dello struct. Assicurarsi di individuare il campo privato e la proprietà pubblica. In questo modo è più semplice visualizzare, a colpo d'occhio, il backsback della proprietà e che il campo è modificabile in base allo script.

Nota

L'unica eccezione a questa è per le strutture di dati che richiedono che i campi vengano serializzati da JsonUtility, dove è necessaria una classe di dati per il funzionamento di tutti i campi pubblici per la serializzazione.

Cosa non fare

private float myValue1;
private float myValue2;

public float MyValue1
{
    get{ return myValue1; }
    set{ myValue1 = value }
}

public float MyValue2
{
    get{ return myValue2; }
    set{ myValue2 = value }
}

Cosa fare

// Enable field to be configurable in the editor and available externally to other scripts (field is correctly serialized in Unity)
[SerializeField]
[ToolTip("If using a tooltip, the text should match the public property's summary documentation, if appropriate.")]
private float myValue; // <- Notice we co-located the backing field above our corresponding property.

/// <summary>
/// If using a tooltip, the text should match the public property's summary documentation, if appropriate.
/// </summary>
public float MyValue
{
    get => myValue;
    set => myValue = value;
}

/// <summary>
/// Getter/Setters not wrapping a value directly should contain documentation comments just as public functions would
/// </summary>
public float AbsMyValue
{
    get
    {
        if (MyValue < 0)
        {
            return -MyValue;
        }

        return MyValue
    }
}

Memorizzare nella cache i valori e serializzarli nella scena/prefab ogni volta che è possibile

Con HoloLens in mente, è consigliabile ottimizzare le prestazioni e i riferimenti alla cache nella scena o prefab per limitare le allocazioni di memoria di runtime.

Cosa non fare

void Update()
{
    gameObject.GetComponent<Renderer>().Foo(Bar);
}

Cosa fare

[SerializeField] // To enable setting the reference in the inspector.
private Renderer myRenderer;

private void Awake()
{
    // If you didn't set it in the inspector, then we cache it on awake.
    if (myRenderer == null)
    {
        myRenderer = gameObject.GetComponent<Renderer>();
    }
}

private void Update()
{
    myRenderer.Foo(Bar);
}

I riferimenti alla cache ai materiali non chiamano il materiale ogni volta

Unity creerà un nuovo materiale ogni volta che si usa ".material", che causerà una perdita di memoria se non è stata pulita correttamente.

Cosa non fare

public class MyClass
{
    void Update()
    {
        Material myMaterial = GetComponent<Renderer>().material;
        myMaterial.SetColor("_Color", Color.White);
    }
}

Cosa fare

// Private references for use inside the class only
public class MyClass
{
    private Material cachedMaterial;

    private void Awake()
    {
        cachedMaterial = GetComponent<Renderer>().material;
    }

    void Update()
    {
        cachedMaterial.SetColor("_Color", Color.White);
    }

    private void OnDestroy()
    {
        Destroy(cachedMaterial);
    }
}

Nota

In alternativa, usare la proprietà "SharedMaterial" di Unity che non crea un nuovo materiale ogni volta che viene fatto riferimento.

Usare la compilazione dipendente dalla piattaforma per assicurarsi che il Toolkit non interrompa la compilazione in un'altra piattaforma

  • Usare WINDOWS_UWP per usare API specifiche della piattaforma UWP e non Unity. Ciò impedisce loro di tentare di eseguire nell'editor o in piattaforme non supportate. Questo è equivalente a UNITY_WSA && !UNITY_EDITOR e deve essere usato a favore di.
  • Usare UNITY_WSA per usare le API Unity specifiche della piattaforma UWP, ad esempio lo UnityEngine.XR.WSA spazio dei nomi. Questa operazione verrà eseguita nell'editor quando la piattaforma è impostata su UWP, nonché in app UWP predefinite.

Questo grafico consente di decidere quale #if usare, a seconda dei casi d'uso e delle impostazioni di compilazione previste.

Piattaforma UWP IL2CPP UWP .NET Editor
UNITY_EDITOR False False True
UNITY_WSA True True True
WINDOWS_UWP True True False
UNITY_WSA && !UNITY_EDITOR True True False
ENABLE_WINMD_SUPPORT True True False
NETFX_CORE False True Falso

Preferisce DateTime.UtcNow su DateTime.Now

DateTime.UtcNow è più veloce di DateTime.Now. Nelle indagini sulle prestazioni precedenti è stato rilevato che l'uso di DateTime.Now comporta un sovraccarico significativo soprattutto quando viene usato nel ciclo Update(). Altri hanno colpito lo stesso problema.

Preferire l'uso di DateTime.UtcNow a meno che non siano effettivamente necessari gli orari localizzati (un motivo legittimo potrebbe essere che si voglia visualizzare l'ora corrente nel fuso orario dell'utente). Se si gestiscono orari relativi (ad esempio, il delta tra un ultimo aggiornamento e ora), è consigliabile usare DateTime.UtcNow per evitare il sovraccarico delle conversioni di fuso orario.

Convenzioni di codifica di PowerShell

Un subset della codebase MRTK usa PowerShell per l'infrastruttura della pipeline e vari script e utilità. Il nuovo codice di PowerShell deve seguire lo stile PoshCode.

Vedi anche

Convenzioni di codifica C# da MSDN