1

Cuando necesito acceder a una Variable Global para usarla en todo el proyecto. Tengo una duda y a la vez una pregunta a raíz de esta pregunta.

Duda:

Suponiendo que tengo la siguiente Clase (Class) :

public static class Funciones
{
   public static string valorGlobal = string.Empty;
}

Para acceder a esta variable: Funciones.valorGlobal; Si de esta manera puedo acceder a asignarle/obtener el valor de la variable.

¿Por qué tengo que encapsular el campo de esta forma? (Duda)

 public static class Funciones
 {
     private static string _valorGlobal = string.Empty;

     public static string valorGlobal
     {
         get { return _valorGlobal; }
         set { _valorGlobal = value; }
     }
 }

Pregunta:

Según las respuestas de esta pregunta ¿Por qué es considerado una mala práctica utilizar variables globales? no me queda clara la solución planteada dependency injection.

Entonces si necesito acceder a un valor en todo el proyecto, ¿Cuál es la forma correcta de hacerlo? (En Código C#)

Nota: Antes de decir que la pregunta es duplicada, favor leer bien la descripción que expongo aquí que no es igual.

J. Rodríguez
  • 5,205
  • 5
  • 29
  • 71
  • La encapsulación usando un property no hace que su uso sea mejor. Sigue siendo un acceso a una variable global con todos los riesgos que esta conlleva, incluyendo el que su acceso no sea *thread safe*. La forma correcta de manejar tu situación depende de qué variable tienes y para qué sirve. No hay suficiente información en este caso para recomendarte algo mejor. – sstan Dec 27 '17 at 19:15
  • usando un `encapsulación` se ve mas estético y solo eso? además de ser mas código. Pero todavía sigue la laguna (Duda) en mi mente de ¿Cuál seria la forma correcta de acceder a un valor desde todo el proyecto sin usar variables globales? – J. Rodríguez Dec 27 '17 at 19:18
  • El que sea vea mejor así o no, es discutible :) Y en cuento a tu duda, bien puede ser que usar una variable global en este caso sea lo correcto, no lo sé, porque no sé cual uso le das a esta variable, si es que cambia el valor y cuando cambia, y cuantos hilos pueden leer/escribir a la variable. Pero muy a menudo, sucede que los programadores usan variables globales porque por pereza no quieren usar parámetros. Esto es mala práctica. Pero aunque fuera correcto tu uso de una variable global, en un ambiente multi hilo, su acceso, con property o no, no me parece *thread safe* a primera vista. – sstan Dec 27 '17 at 19:23
  • @sstan, un ejemplo puede ser: **Para almacenar el código del usuario logeado en el sistema** ... – J. Rodríguez Dec 27 '17 at 19:47
  • En este caso, mi pregunta sería: ¿puedes tener más de un usuario logeado en tu sistema? Obviamente, en ese caso, no puedes usar una variable global estática como la tienes. Pero si solo puedes tener un solo usuario, entonces tal vez convenga que tu variable sea definida y usada cómo si fuera una constante (`readonly` tal vez?), en cual caso, el que la variable sea global no es un problema. Pero aun en este caso, habría que tener cuidado dependiendo de cómo y cuando inicializas el valor, para que el código sea *thread safe*. – sstan Dec 27 '17 at 19:55
  • @sstan _¿puedes tener más de un usuario logeado en tu sistema?_ **Si** , debo admitir mas de un usuario conectado al sistema. ¿cómo la uso? – J. Rodríguez Dec 27 '17 at 19:58
  • Ahora te hago una pregunta: En que caso necesitas usar una variable global? – Alvarez Dec 27 '17 at 20:18
  • @LuisFernando, Ejemplo: _El usuario se logea en el sistema, ese código del usuario se almacena en la variable, **posteriormente cuando carga la pantalla principal (usar ese código para hacer un select y mostrar el nombre del usuario)** otro caso podría ser debo almacenar unos datos de un procedimiento de almacenado y el procedimiento recibe parámetros entre ellos el `código del usuario`_ Puede ser un tipo de uso así. – J. Rodríguez Dec 27 '17 at 20:30
  • Pero para no complicarte la vida puedes crear el form principal recibiendo como parametro desde el login el nombre del empleado por ejemplo. Y estando en el form principal podras acceder a ella para usarla en cualquier cosa. Asignarla a un textbox, label y muchos mas. – Alvarez Dec 27 '17 at 20:35
  • @LuisFernando no puedo hacerlo como parámetro del formulario principal, porque esa variable no solo se usará en ese formulario, cabe la posibilidad de usarla en muchos mas, ademas incluso el código podría ser usado en otra clase que contenga otras funciones. – J. Rodríguez Dec 27 '17 at 20:38
  • Bueno, suerte con eso. – Alvarez Dec 27 '17 at 20:40
  • @LuisFernando, jaja abandonaste :P – J. Rodríguez Dec 27 '17 at 20:41
  • 1
    Aun en el último caso que planteas persiste el problema: si tienes el `código de usuario` como una variable global ¿qué **GARANTIZA** que cuando el usuario 2 invoque un método sea ese código el que entre como parámetro y no el código del usuario 1? El problema con las variables globales es que, si más de una acción puede cambiar su valor ¿cómo garantizas que ese valor será válido para cualquier punto del programa? Una cosa son las [constantes](https://www.exceptionnotfound.net/const-vs-static-vs-readonly-in-c-sharp-applications/) y otra son las variables "alterables". – Kroneaux Schneider Dec 27 '17 at 20:41
  • @KroneauxSchneider, por tal razón que expresas es que necesito una forma en la que pueda acceder a ese valor del usuario, Pero como planteas que no se confunda el valor del usuario que solicita la variable, es decir poder hacerlo sin que (cuando el usuario 1 tenga X valor) y se logea el proximo usuario y ambos estan en el sistema. Evitar el que el valor entre ambos usuario se confunda. Cómo logro esto? – J. Rodríguez Dec 27 '17 at 20:44
  • Vamos al [chat](https://chat.stackexchange.com/rooms/70874/discussion-between-j-rodriguez-and-luis-fernando) – Kroneaux Schneider Dec 27 '17 at 20:48

2 Answers2

3

En cuanto a la duda, como ya te han explicado en los comentarios, es más un tema estético que práctico.

En mi caso yo suelo preferir para esos casos propiedades autoencapsuladas:

public static string ValorGlobal { get; set; } = string.Empty;

pero es más una manía personal que una buena práctica.

Lo que sí me parece importante es que, una vez que decidas definir estos casos de una forma, lo hagas siempre de la misma forma. Especialmente si hay varias personas trabajando sobre el mismo proyecto. Pero, aunque seas tú el único que trabaja sobre él, a ti mismo te resultará más fácil leer tu propio código si siempre sigues las mismas convenciones.

En cuanto al uso de variables globales ya te explicaban en la otra pregunta que el hecho de que se desaconsejen no quiere decir que no puedan usarse nunca, lo que quiere decir es que normalmente se usan más de lo debido. En muchas ocasiones se utilizan por comodidad, y así ahorrarse trabajo de estructurar el código, y acaban dando muchos problemas. Uno de los motivos principales, como te indicaban, es que pierdes la visión de las dependencias internas de tu código. Y así llegamos a tu pregunta: ¿cómo solucionamos ese problema de mantener las dependencias del código a través de la "inyección de dependencias"?

Voy a ver si consigo aclararlo. No pretendo que esto sea una especie de tutorial sobre la inyección de dependencias, simplemente trataré de aclarar el concepto.

Pongamos que, como comentas, tenemos un formulario en cuyo código necesitamos el nombre del usuario conectado. Podríamos crear una variable global en la aplicación y acceder a ella desde el formulario. El problema es que la única forma de saber que el funcionamiento del formulario depende del usuario conectado es bucear en el código para ver si en algún lugar se accede a la variable global.

Lo que propone la inyección de dependencias es que el código del formulario no acceda a elementos externos, si no que estos sean "inyectados" dentro del formulario. Uno de los métodos más utilizados es la inyección a través de constructor que es el que te proponían. Consistiría en añadir un argumento al constructor para recibir el nombre de usuario y que sea el valor de este argumento el que se utilice en el código. Algo así:

public partial class Form1 : Form
{
    public Form1(string nombreUsuario)
    {
        InitializeComponent();
        Text = $@"Usuario: {nombreUsuario}";
    }
}

De esta forma viendo el constructor ya ves las dependencias que tiene el elemento. Además es mucho más testeable: si quieres ver si el formulario funciona con diferentes usuarios no tendrías más que instanciarlo con diferentes nombres:

new Form1("Pepe");
new Form1("Susana");
new Form1("Juan");

Evidentemente esto implica que en todos los lugares en los que se instancia el formulario debería cambiarse la llamada de

new Form1();

a

new Form1(nombreUsuario);

Además, si en el código del formulario se necesita acceder a otro elemento externo, habría que volver a modificar el constructor y, por lo tanto, todos los lugares donde se creen instancias del formulario.

Para evitar este problema existen los "contenedores de dependencias". Los contenedores de dependencias son elementos en los que se registran las diferentes dependencias externas y se encarga de crear las instancias de los diferentes elementos inyectándoles las dependencias necesarias.

Para nuestro ejemplo podríamos crear un contenedor de dependencias sencillo:

public static class Contenedor
{
    public static string NombreUsuario { get; set; }

    public static T Crear<T>() where T : class
    {
        // Si tiene constructor sin parámetros lo usamos
        var constructor = typeof(T).GetConstructor(new Type[]{});
        if (constructor != null)
        {
            return (T) constructor.Invoke(new object[] { });
        }
        // Si tiene constructor con un argumento string lo usamos pasándole el
        // valor de la propiedad NombreUsuario
        constructor = typeof(T).GetConstructor(new[] {typeof(string)});
        if (constructor != null)
        {
            return (T) constructor.Invoke(new object[] {NombreUsuario});
        }

        throw new Exception("No se ha encontrado un constructor válido");
    }
}

El contenedor tiene una propiedad NombreUsuario a través de la cual se le puede dar un valor a la dependencia nombre de usuario.

Tiene también un método genérico Crear que se encarga de crear y devolver una nueva instancia de un tipo dado. Este método comprueba si el tipo tiene un constructor sin parámetros y, si es así, lo utiliza para crear la nueva instancia. Si no, comprueba si tiene un constructor con un único parámetro de tipo string y, si es así, lo utiliza para crear la instancia pasándole como parámetro el nombre de usuario. Si no encuentra uno de estos constructores genera una excepción indicando que el tipo no tiene un constructor válido.

De esta forma en nuestra aplicación podríamos crear una nueva instancia del formulario utilizando el método Crear del contenedor de dependencias, sustituyendo

new Form1(nombreUsuario);

por

Contenedor.Crear<Form1>();

De esta forma si en el futuro se añade una nueva dependencia a Form1 únicamente habría que modificar el método Crear del contenedor para que sea capaz de inyectar esta nueva dependencia. Sin necesitar modificar todos los lugares donde se crean instancias del formulario.

Previamente deberemos haber establecido el valor de la dependencia "nombre de usuario" en el contenedor de dependencias:

Contenedor.NombreUsuario = UsuarioLogin.Nombre;

Por supuesto la implementación de este contenedor de dependencias puede ser una tarea muy compleja (aunque muy educativa también), pero existen contenedores de dependencias ya implementados que dan una gran funcionalidad y rendimiento como Autofac o Ninject.

Puedes ampliar información en este artículo en el que explico lo que es la inyección de dependencias utilizando ejemplos con Ninject:

Ninject. Hola Mundo (I). ¿Qué es la inyección de dependencias?

Espero que ayude.

Asier Villanueva
  • 14,299
  • 2
  • 13
  • 31
  • Buena explicación comprendo, estoy usando `Visual Studio 2010` con `NET Framework 4` no me permite asignar el valor asi: `public static string ValorGlobal { get; set; } = string.Empty;` o de esta forma `public static string ValorGlobal { get; set; } ="Ejemplo"'` – J. Rodríguez Jan 02 '18 at 12:15
  • Sí, si no recuerdo mal, esta forma de inicializar las propiedades autoimplementadas se introdujo en la versión 6 de C#. En cualquier caso, como comentaba en la respuesta, es más un tema estético que práctico. – Asier Villanueva Jan 02 '18 at 12:23
  • Comprendo está bien, gracias por tomarse el tiempo de responder. – J. Rodríguez Jan 02 '18 at 12:25
0

Yo trabajo las variables globales de la siguiente forma:

En el archivo Program.cs creo la siguiente clase:

static class Globales
  {
    public static string connectionString;
    public static int id_empresa;
    public static int id_empresa_sucursal;
  }

Y en la clase main hago lo siguiente:

static void Main()
        {
            Globales.connectionString = @"Server=192.168.134.128;Database=contabilidad;Uid=contabilidad;Pwd=m5jizO6aTav47uGIsoX5ro1875Nap5.;";
            Globales.id_empresa = 1;
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new frmPrincipal());
        }

Y en cualquier parte del código puedo hacer la llamada a la variable de la siguiente forma:

int id_empresa = Globales.id_empresa;