Convenciones de código de C#
Las convenciones de codificación tienen los objetivos siguientes:
- Crean una apariencia coherente en el código, para que los lectores puedan centrarse en el contenido, no en el diseño.
- Permiten a los lectores comprender el código más rápidamente al hacer suposiciones basadas en la experiencia anterior.
- Facilitan la copia, el cambio y el mantenimiento del código.
- Muestran los procedimientos recomendados de C#.
Importante
Microsoft usa las instrucciones de este artículo para desarrollar ejemplos y documentación. Se adoptaron a partir de las instrucciones de estilo de codificación de .NET Runtime y C#. Puede usarlas o adaptarlas a sus necesidades. Los objetivos principales son la coherencia y la legibilidad dentro del proyecto, el equipo, la organización o el código fuente de la empresa.
Convenciones de nomenclatura
Hay varias convenciones de nomenclatura que se deben tener en cuenta al escribir código de C#.
En los ejemplos siguientes, cualquiera de las instrucciones relativas a los elementos marcados public
también es aplicable cuando se trabaja con elementos protected
y protected internal
, los cuales están diseñados para ser visibles para los autores de llamadas externos.
Pascal case
Use la grafía Pascal ("PascalCasing") al asignar un nombre a class
, record
o struct
.
public class DataService
{
}
public record PhysicalAddress(
string Street,
string City,
string StateOrProvince,
string ZipCode);
public struct ValueCoordinate
{
}
Al asignar un nombre a interface
, use la grafía Pascal además de agregar el prefijo I
al nombre. Esto indica claramente a los consumidores que es un elemento interface
.
public interface IWorkerQueue
{
}
Al asignar nombres a miembros public
de tipos, como campos, propiedades, eventos, métodos y funciones locales, use la grafía Pascal.
public class ExampleEvents
{
// A public field, these should be used sparingly
public bool IsValid;
// An init-only property
public IWorkerQueue WorkerQueue { get; init; }
// An event
public event Action EventProcessing;
// Method
public void StartEventProcessing()
{
// Local function
static int CountQueueItems() => WorkerQueue.Count;
// ...
}
}
Al escribir registros posicionales, use la grafía Pascal para los parámetros, ya que son las propiedades públicas del registro.
public record PhysicalAddress(
string Street,
string City,
string StateOrProvince,
string ZipCode);
Para más información sobre los registros posicionales, consulte Sintaxis posicional para la definición de propiedades.
Grafía Camel
Use la grafía Camel ("camelCasing") al asignar nombres a campos private
o internal
, y prefijos con _
.
public class DataService
{
private IWorkerQueue _workerQueue;
}
Sugerencia
Al editar código de C# que sigue estas convenciones de nomenclatura en un IDE que admite la finalización de instrucciones, al escribir _
se mostrarán todos los miembros con ámbito de objeto.
Al trabajar con campos static
que sean private
o internal
, use el prefijo s_
y, para el subproceso estático, use t_
.
public class DataService
{
private static IWorkerQueue s_workerQueue;
[ThreadStatic]
private static TimeSpan t_timeSpan;
}
Al escribir parámetros de método, use la grafía Camel.
public T SomeMethod<T>(int someNumber, bool isValid)
{
}
Para obtener más información sobre las convenciones de nomenclatura de C#, vea Estilo de codificación de C#.
Convenciones de nomenclatura adicionales
En ejemplos que no incluyan directivas using, use calificaciones de espacio de nombres. Si sabe que un espacio de nombres se importa en un proyecto de forma predeterminada, no es necesario completar los nombres de ese espacio de nombres. Los nombres completos pueden partirse después de un punto (.) si son demasiado largos para una sola línea, como se muestra en el ejemplo siguiente.
var currentPerformanceCounterCategory = new System.Diagnostics. PerformanceCounterCategory();
No es necesario cambiar los nombres de objetos que se crearon con las herramientas del diseñador de Visual Studio para que se ajusten a otras directrices.
Convenciones de diseño
Un buen diseño utiliza un formato que destaque la estructura del código y haga que el código sea más fácil de leer. Las muestras y ejemplos de Microsoft cumplen las convenciones siguientes:
Utilice la configuración del Editor de código predeterminada (sangría automática, sangrías de 4 caracteres, tabulaciones guardadas como espacios). Para obtener más información, vea Opciones, editor de texto, C#, formato.
Escriba solo una instrucción por línea.
Escriba solo una declaración por línea.
Si a las líneas de continuación no se les aplica sangría automáticamente, hágalo con una tabulación (cuatro espacios).
Agregue al menos una línea en blanco entre las definiciones de método y las de propiedad.
Utilice paréntesis para que las cláusulas de una expresión sean evidentes, como se muestra en el código siguiente.
if ((val1 > val2) && (val1 > val3)) { // Take appropriate action. }
Colocación de directivas using fuera de la declaración del espacio de nombres
Cuando una directiva using
está fuera de la declaración de un espacio de nombres, ese espacio de nombres importado es su nombre completo. Eso es más claro. Cuando la directiva using
está dentro del espacio de nombres, puede ser relativa al espacio de nombres o su nombre completo. Eso es ambiguo.
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
…
}
}
}
Se supone que hay una referencia (directa o indirecta) a la clase WaitUntil.
Ahora vamos a cambiarla ligeramente:
namespace CoolStuff.AwesomeFeature
{
using Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
…
}
}
}
Y se compila hoy. Y mañana. Sin embargo, en algún momento de la próxima semana, este código (sin modificar) produce dos errores:
- error CS0246: The type or namespace name 'WaitUntil' could not be found (are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context
Una de las dependencias ha introducido esta clase en un espacio de nombres y, después, finaliza con .Azure
:
namespace CoolStuff.Azure
{
public class SecretsManagement
{
public string FetchFromKeyVault(string vaultId, string secretId) { return null; }
}
}
Una directiva using
colocada dentro de un espacio de nombres es contextual y complica la resolución de nombres. En este ejemplo, es el primer espacio de nombres que encuentra.
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
Si se agrega un nuevo espacio de nombres que coincida con CoolStuff.Azure
o CoolStuff.AwesomeFeature.Azure
, se tomaría como coincidencia antes que el espacio de nombres global Azure
. Para resolverlo, agregue el modificador global::
a la declaración using
. Sin embargo, es más fácil colocar declaraciones using
fuera del espacio de nombres.
namespace CoolStuff.AwesomeFeature
{
using global::Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
…
}
}
}
Convenciones de los comentarios
Coloque el comentario en una línea independiente, no al final de una línea de código.
Comience el texto del comentario con una letra mayúscula.
Finalice el texto del comentario con un punto.
Inserte un espacio entre el delimitador de comentario (//) y el texto del comentario, como se muestra en el ejemplo siguiente.
// The following declaration creates a query. It does not run // the query.
No crees bloques de formato con asteriscos alrededor de los comentarios.
Asegúrese de que todos los miembros públicos tengan los comentarios XML necesarios que proporcionan descripciones adecuadas sobre su comportamiento.
Convenciones de lenguaje
En las secciones siguientes se describen las prácticas que sigue el equipo C# para preparar las muestras y ejemplos de código.
Tipo de datos String
Use interpolación de cadenas para concatenar cadenas cortas, como se muestra en el código siguiente.
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
Para anexar cadenas en bucles, especialmente cuando se trabaja con grandes cantidades de texto, utilice un objeto StringBuilder.
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
Variables locales con tipo implícito
Use tipos implícitos para las variables locales cuando el tipo de la variable sea obvio desde el lado derecho de la asignación, o cuando el tipo exacto no sea importante.
var var1 = "This is clearly a string."; var var2 = 27;
No use var cuando el tipo no sea evidente desde el lado derecho de la asignación. No asuma que el tipo está claro a partir de un nombre de método. Se considera que un tipo de variable es claro si es un operador
new
o una conversión explícita.int var3 = Convert.ToInt32(Console.ReadLine()); int var4 = ExampleClass.ResultSoFar();
No confíe en el nombre de variable para especificar el tipo de la variable. Puede no ser correcto. En el siguiente ejemplo, el nombre de la variable
inputInt
es engañoso. Es una cadena.var inputInt = Console.ReadLine(); Console.WriteLine(inputInt);
Evite el uso de
var
en lugar de dynamic. Usedynamic
cuando desee la inferencia de tipos en tiempo de ejecución. Para obtener más información, vea Uso de tipo dinámico (Guía de programación de C#).Use tipos implícitos para determinar el tipo de la variable de bucle en bucles
for
.En el ejemplo siguiente se usan tipos implícitos en una instrucción
for
.var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
No use tipos implícitos para determinar el tipo de la variable de bucle en bucles
foreach
. En la mayoría de los casos, el tipo de elementos de la colección no es inmediatamente obvio. El nombre de la colección no debe servir únicamente para inferir el tipo de sus elementos.En el ejemplo siguiente se usan tipos explícitos en una instrucción
foreach
.foreach (char ch in laugh) { if (ch == 'h') Console.Write("H"); else Console.Write(ch); } Console.WriteLine();
Nota
Tenga cuidado de no cambiar accidentalmente un tipo de elemento de la colección iterable. Por ejemplo, es fácil cambiar de System.Linq.IQueryable a System.Collections.IEnumerable en una instrucción
foreach
, lo cual cambia la ejecución de una consulta.
Tipos de datos sin signo
En general, utilice int
en lugar de tipos sin signo. El uso de int
es común en todo C#, y es más fácil interactuar con otras bibliotecas cuando se usa int
.
Matrices
Utilice sintaxis concisa para inicializar las matrices en la línea de declaración. En el siguiente ejemplo, observe que no puede utilizar var
en lugar de string[]
.
string[] vowels1 = { "a", "e", "i", "o", "u" };
Si usa la creación de instancias explícita, puede usar var
.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };
Delegados
Use Func<>
y Action<>
en lugar de definir tipos de delegado. En una clase, defina el método delegado.
public static Action<string> ActionExample1 = x => Console.WriteLine($"x is: {x}");
public static Action<string, string> ActionExample2 = (x, y) =>
Console.WriteLine($"x is: {x}, y is {y}");
public static Func<string, int> FuncExample1 = x => Convert.ToInt32(x);
public static Func<int, int, int> FuncExample2 = (x, y) => x + y;
Llame al método con la signatura definida por el delegado Func<>
o Action<>
.
ActionExample1("string for x");
ActionExample2("string for x", "string for y");
Console.WriteLine($"The value is {FuncExample1("1")}");
Console.WriteLine($"The sum is {FuncExample2(1, 2)}");
Si crea instancias de un tipo de delegado, utilice la sintaxis concisa. En una clase, defina el tipo de delegado y un método que tenga una firma coincidente.
public delegate void Del(string message);
public static void DelMethod(string str)
{
Console.WriteLine("DelMethod argument: {0}", str);
}
Cree una instancia del tipo de delegado y llámela. La siguiente declaración muestra la sintaxis condensada.
Del exampleDel2 = DelMethod;
exampleDel2("Hey");
La siguiente declaración utiliza la sintaxis completa.
Del exampleDel1 = new Del(DelMethod);
exampleDel1("Hey");
Instrucciones try-catch
y using
en el control de excepciones
Use una instrucción try-catch en la mayoría de casos de control de excepciones.
static string GetValueFromArray(string[] array, int index) { try { return array[index]; } catch (System.IndexOutOfRangeException ex) { Console.WriteLine("Index is out of range: {0}", index); throw; } }
Simplifique el código mediante la instrucción using de C#. Si tiene una instrucción try-finally en la que el único código del bloque
finally
es una llamada al método Dispose, use en su lugar una instrucciónusing
.En el ejemplo siguiente, la instrucción
try-finally
solo llama aDispose
en el bloquefinally
.Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) { ((IDisposable)font1).Dispose(); } }
Puede hacer lo mismo con una instrucción
using
.using (Font font2 = new Font("Arial", 10.0f)) { byte charset2 = font2.GdiCharSet; }
Use la nueva sintaxis
using
que no requiere corchetes:using Font font3 = new Font("Arial", 10.0f); byte charset3 = font3.GdiCharSet;
Operadores &&
y ||
Para evitar excepciones y aumentar el rendimiento omitiendo las comparaciones innecesarias, use &&
en lugar de &
y ||
en lugar de |
cuando realice comparaciones, como se muestra en el ejemplo siguiente.
Console.Write("Enter a dividend: ");
int dividend = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter a divisor: ");
int divisor = Convert.ToInt32(Console.ReadLine());
if ((divisor != 0) && (dividend / divisor > 0))
{
Console.WriteLine("Quotient: {0}", dividend / divisor);
}
else
{
Console.WriteLine("Attempted division by 0 ends up here.");
}
Si el divisor es 0, la segunda cláusula de la instrucción if
produciría un error en tiempo de ejecución. Pero el operador && se cortocircuita cuando la primera expresión es falsa. Es decir, no evalúa la segunda expresión. El operador & evaluaría ambas, lo que provocaría un error en tiempo de ejecución cuando divisor
es 0.
Operador new
Use una de las formas concisas de creación de instancias de objeto, tal como se muestra en las declaraciones siguientes. En el segundo ejemplo se muestra la sintaxis que está disponible a partir de C# 9.
var instance1 = new ExampleClass();
ExampleClass instance2 = new();
Las declaraciones anteriores son equivalentes a la siguiente declaración.
ExampleClass instance2 = new ExampleClass();
Use inicializadores de objeto para simplificar la creación de objetos, tal y como se muestra en el ejemplo siguiente.
var instance3 = new ExampleClass { Name = "Desktop", ID = 37414, Location = "Redmond", Age = 2.3 };
En el ejemplo siguiente se establecen las mismas propiedades que en el ejemplo anterior, pero no se utilizan inicializadores.
var instance4 = new ExampleClass(); instance4.Name = "Desktop"; instance4.ID = 37414; instance4.Location = "Redmond"; instance4.Age = 2.3;
Control de eventos
Si va a definir un controlador de eventos que no es necesario quitar más tarde, utilice una expresión lambda.
public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
La expresión lambda acorta la siguiente definición tradicional.
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object? sender, EventArgs e)
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
Miembros estáticos
Llame a miembros estáticos con el nombre de clase: ClassName.StaticMember. Esta práctica hace que el código sea más legible al clarificar el acceso estático. No califique un miembro estático definido en una clase base con el nombre de una clase derivada. Mientras el código se compila, su legibilidad se presta a confusión, y puede interrumpirse en el futuro si se agrega a un miembro estático con el mismo nombre a la clase derivada.
Consultas LINQ
Utilice nombres descriptivos para las variables de consulta. En el ejemplo siguiente, se utiliza
seattleCustomers
para los clientes que se encuentran en Seattle.var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Utilice alias para asegurarse de que los nombres de propiedad de tipos anónimos se escriben correctamente con mayúscula o minúscula, usando para ello la grafía Pascal.
var localDistributors = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { Customer = customer, Distributor = distributor };
Cambie el nombre de las propiedades cuando puedan ser ambiguos en el resultado. Por ejemplo, si la consulta devuelve un nombre de cliente y un identificador de distribuidor, en lugar de dejarlos como
Name
eID
en el resultado, cambie su nombre para aclarar queName
es el nombre de un cliente eID
es el identificador de un distribuidor.var localDistributors2 = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { CustomerName = customer.Name, DistributorID = distributor.ID };
Utilice tipos implícitos en la declaración de variables de consulta y variables de intervalo.
var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Alinee las cláusulas de consulta bajo la cláusula
from
, como se muestra en los ejemplos anteriores.Use cláusulas
where
antes de otras cláusulas de consulta para asegurarse de que las cláusulas de consulta posteriores operan en un conjunto de datos reducido y filtrado.var seattleCustomers2 = from customer in customers where customer.City == "Seattle" orderby customer.Name select customer;
Use varias cláusulas
from
en lugar de una cláusulajoin
para obtener acceso a colecciones internas. Por ejemplo, una colección de objetosStudent
podría contener cada uno un conjunto de resultados de exámenes. Cuando se ejecuta la siguiente consulta, devuelve cada resultado superior a 90, además del apellido del alumno que recibió la puntuación.var scoreQuery = from student in students from score in student.Scores! where score > 90 select new { Last = student.LastName, score };
Seguridad
Siga las instrucciones de Instrucciones de codificación segura.