sábado, 21 de mayo de 2022

Curso avanzado de C#. Manejo de tipos dinámicos

Los atributos DllImport y MarshalAs descritos en el post anterior permiten decirle al programa dónde encontrar un método no administrado y qué tipos de datos utiliza para los parámetros y finalmente, un tipo de retorno. Esto permite que el programa invoque métodos no administrados a través de P/invoke.

COM Interop proporciona otra forma en que un programa administrado puede interactuar con código no administrado. Para usar COM Interop, necesitamos dar a nuestro   programa una referencia a una biblioteca apropiada. Para ello,  buscamos en el Explorador de soluciones, hacemos clic con el botón derecho en Referencias y seleccionamos Agregar referencia.

Manejo de tipos dinámicos C#

Buscamos la referencia que deseamos agregar en la sección Bibliotecas de tipos de la pestaña COM (por ejemplo, Biblioteca de Microsoft ActiveX Data Objects 2.0), marcamos la casilla junto a la entrada y aceptamos.

Agregar una referencia .NET con Visual Studio

Agregar una referencia de la biblioteca le dice a nuestro programa (y Visual Studio) mucho sobre la aplicación COM no administrada. Si abrimos el menú Ver y seleccionamos Explorador de objetos, podemos utilizarlo para buscar entre los objetos y tipos definidos por la biblioteca.

Examinador de Objetos Visual Studio


La biblioteca brinda a Visual Studio suficiente información para que proporcione IntelliSense sobre algunos de los miembros de la biblioteca, pero es posible que Visual Studio aún no comprenda todos los tipos usados por la biblioteca. 

Examinador de Objetos Visual Studio

Desde la versión 4.0 C# proporciona un tipo de datos especial llamado dinámico (dynamic) que podemos utilizar en esta situación. Este es un tipo de datos estático, pero su tipo verdadero no se evalúa hasta el momento de la ejecución. En el momento del diseño y la compilación, C# no evalúa el tipo de elemento dinámico, por lo que no marca errores de sintaxis para problemas tales como discrepancias de tipos porque aún no ha evaluado el tipo dinámico. Esto puede resultar útil si no podemos proporcionar información completa sobre el tipo de un elemento al compilador.

C# considera que los objetos definidos por el código COM Interop no administrado tienen tipo dinámico (dynamic),  como se ha indicado, espera hasta el tiempo de ejecución para ver si el código tiene sentido.

El programa de ejemplo ExcelInterop, utiliza el siguiente código para crear un libro de excel Microsoft Excel (previamente hemos tenido que añadir la referencia Microsoft Excel 14.0 Object Library)  :

 // Abre la aplicación excel

Excel._Application excelApp = New Excel.Application();

// añade un libro

Excel.Workbook nuevo_libro = excelApp.Workbooks.Add();

Excel.Worksheet sheet = nuevo_libro.Worksheets[1];

// Muestra el Excel.

excelApp.Visible = true;

// Pone cabeceras a las columnas.

sheet.Cells[1, 1].Value = "Valor";

sheet.Cells[1, 2].Value = "Cuadrado del Valor";

// Muestra los primeros 10 cuadrados.

For (int i = 1; i <= 10; i++)

{

sheet.Cells[i + 1, 1].Value = i;

sheet.Cells[i + 1, 2].Value = (i * i).ToString();

}

// Rellena las columnas.

sheet.Columns[1].AutoFit();

sheet.Columns[2].AutoFit();

En este código, el tipo de datos dinámicos se usa implícitamente en un par de lugares. Visual Studio no comprende realmente el tipo de datos de la hoja. Cells[1, 1], por lo que difiere la verificación de tipos para ese valor. Eso permite que el programa se refiera a la propiedad Value de esta entidad aunque el programa no sepa si la celda tiene tal propiedad. En realidad, podríamos intentar establecer sheet.Cells[1, 1].Whatever = i y Visual Studio no mostraría error hasta el momento de la ejecución, cuando intente acceder a la propiedad Whatever y descubra que no existe.

 De forma similar, Visual Studio trata a sheet.Columns[1] como si tuvieran un tipo dinámico, por lo que no sabe que el método AutoFit existe hasta el momento de la ejecución. En es siguiente código veremos un ejemplo más específico de C#.

// rellenamos un array de numeros.

int[]array1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// No funciona bien por que array1.Clone es un objeto.

//int[] array2 = array1.Clone();

// Esto si funciona.

int[]array3 = (int[])array1.Clone();

array3[5] = 55;

// Esto también funciona.

dynamic array4 = array1.Clone();

array4[6] = 66;

array4[7] = "Esto no funciona";

 Este código inicializa una matriz de números enteros. El código comentado intenta usar el método Clone de la matriz para hacer una copia de la matriz. Pero el método Clone devuelve un objeto no específico, por lo que el código no puede guardarlo en una variable que se refiere a una matriz de int. Y falla. La siguiente declaración introduce correctamente el objeto en un int[] para que funcione. Después, el código almacena un nuevo valor entero en la matriz. Después, el código declara un array4 que tiene el tipo dinámico.  Se clona el array y guarda la copia en la variable array4. En tiempo de ejecución, el programa puede decir que la copia es en realidad un int[10], por lo que ese es el tipo de datos que asigna a array4.

La sentencia final intenta guardar una cadena en array4[7]. En diseño y compilación, Visual Studio no intenta validar esta declaración porque array4 se declaró dinámico. Sin embargo, en tiempo de ejecución, esto falla porque array4 es en realidad un int[] y no puede contener una cadena.

El tipo de datos dinámicos permiten evitar errores de sintaxis cuando no conocemos (o no podemos saber) el tipo de un objeto en el momento de la compilación.

Desafortunadamente, no saber el tipo de un objeto en el momento del diseño también significa que Visual Studio no puede proporcionar verificación de tipo o IntelliSense. Eso significa que debemos asegurarnos de que los métodos que invoquemos realmente existan, que asignemos valores específicos a una variable o propiedad dinámica y que no intentemos guardar un valor dinámico en una variable incompatible. El programa mostrará los errores durante la ejecución, pero no recibiremos mucha ayuda durante su diseño y compilación.

Para evitar este tipo de errores en tiempo de ejecución, debemos evitar el tipo de datos dinámicos y utilizar tipos de datos más específicos siempre que sea posible

Ejercicio de ejemplo  Formulario de pedidos

Creamos un formulario de pedidos como similar a este.

Manejo de tipos dinámicos


Este formulario de entrada de pedidos analiza (parsea) los valores numéricos y de moneda introducidos por el usuario. Cuando el usuario hace clic en el botón Aceptar, se valida el formulario, calcula y muestra los valores apropiados. (No es necesario preocuparse por formatear los campos de salida como moneda. Solo utilizaremos métodos
ToString de las variables para mostrar el texto.) Si todos los valores introducidos por el usuario son válidos, se muestra un mensaje que indica que el pedido está bien y nos pregunta si queremos continuar. Si decimos que sí

el programa realiza las siguientes operaciones:

Si alguno de los campos de una fila no está en blanco, el resto de campos de esa fila no deben estar en blanco.

La cantidad es un número entero entre 1 y 100.

El precio debe estar entre 0.01 € y $ 100,000.00 € (Hay que asegurarse  de permitir valores con formato de moneda).

La tasa de impuestos es un decimal entre 0,00 y 0,20. (No hay que preocuparse por valores porcentuales como el 7% por ciento.


No tenemos que olvidarnos de agregar una declaración using System.Globalization para poder usar NumberStyles.

Hay varias formas de estructurar el código del programa para que sea más fácil de usar y mantener. 

Debemos considerar escribir los siguientes métodos:

-DisplayErrorMessage que muestre un mensaje de error estándar y establezca el foco en un TextBox que tenga el valor no válido o faltante.

-ValidateRequiredTextBox deberá verificar que los TextBox no queden en blanco (sin rellenar).

-ValidateRow validará una fila de entrada que consta de Descripción, Cantidad y Precio de cada cuadro de texto.

Para obtener un valor de un TextBox, utilizaremos el método TryParse apropiado. Por ejemplo, el siguiente código muestra cómo leer un valor de Precio Unidad:

 // Intenta analizar priceEach.

If (!decimal.TryParse(TxtPrecio.Text, NumberStyles.Currency,

null, out precioUnidad))

{

    DisplayErrorMessage(

    "Formato no válido. El precio debe ser un     valor de tipo currency.",

    "Formáto no válido", TxtPrecio);

    Return True;

}

Este código utiliza NumberStyles.Currency para habilitar los valores de moneda.

- Debemos utilizar sentencias if para determinar si los valores se encuentran dentro de los límites esperados. El siguiente código muestra cómo el programa puede validar un valor de Price Each:

// Nos aseguramos de que el precio esté entre 0,01 y 100.000,00.

If ((priceEach < 0.01m) || (precioUnidad > 100000.00m))

{

DisplayErrorMessage(

"Formato no válido. El precio debe estar entre 0,01 y 100000,00.",

"Cantidad no válida", TxtPrecio);

Return True;

}

-Calculamos y mostramos los valores de Precio ampliado, Subtotal, Impuesto sobre las ventas y Total general. El siguiente código muestra cómo procesar la primera fila del formulario de pedido: 

subtotal = 0;

If (ValidateRow(txtDescripcion, txtCantidad, txtPrecioUnidad,

out Cantidad, out PrecioUnidad)) Return;

Precio = Cantidad * PrecioUnidad;

If (Precio == 0m) txtPrecio.Clear();

Else txtPrecio.Text = Precio.ToString();

subtotal += Precio;

Este código llama al método ValidateRow para validar y obtener los valores de Descripción, Cantidad y Precio de la primera fila. Si ese método devuelve un error al devolver true, el código retorna. Si la fila no contiene error, el código calcula Precio y muestra su valor en el TextBox apropiado. Después añade el precio extendido de la fila al valor subtotal actual y continúa procesando las otras filas.

Una forma obvia de mejorar la interfaz de usuario sería eliminar los cuadros de texto de cantidad y reemplazarlos con controles NumericUpDown. Así, el usuario puede seleccionar un valor dentro de los valores mínimos y máximos permitidos. El usuario no podía escribir basura y no podía seleccionar valores fuera del rango permitido. Incluso podemos utilizar un control NumericUpDown para la tasa impositiva estableciendo sus propiedades Mínimo = 0, Máximo = 0,2, Incremento = 0.05 y DecimalPlaces = 2.

También podemos utilizar los controles NumericUpDown para los campos PrecioUnidad, pero ese control hace que se introduzcan valores monetarios incorrectos. En general, es mejor permitir que los usuarios seleccionen un valor en lugar de introducir uno en un TextBox para que no puedan ingresar valores no válidos.

A continuación el ejemplo de código completo:

using System;

using System.Windows.Forms;

using System.Globalization;

 

namespace Ejemplo_Tipos_Dinamicos

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        // Salida.

        private void cancelButton_Click(object sender, EventArgs e)

        {

            Close();

        }


        // Valida los valores en el formulario. Si hay errores,

        // indica al usuario y establece el foco en el TextBox apropiado.

        // Si no hay errores, muestra un mensaje de éxito y sale.

        private void okButton_Click(object sender, EventArgs e)

        {

            int cantidad;

            decimal precioUnitario, precio, subtotal;

 

            // Valida las filas. Si alguna falla, regresa.

            subtotal = 0;

            if (ValidaFila(txtDescripcion1, txtCantidad1, txtPrecioUnitario1,

                out cantidad, out precioUnitario)) return;

            precio = cantidad * precioUnitario;

            if (precio == 0m) txtPrecio1.Clear();

            else txtPrecio1.Text = precio.ToString("C");

            subtotal += precio;

 

            if (ValidaFila(txtDescripcion2, txtCantidad2, txtPrecioUnitario2,

                out cantidad, out precioUnitario)) return;

            precio = cantidad * precioUnitario;

            if (precio == 0m) txtPrecio2.Clear();

            else txtPrecio2.Text = precio.ToString("C");

            subtotal += precio;

 

            if (ValidaFila(txtDescripcion3, txtCantidad3, txtPrecioUnitario3,

                out cantidad, out precioUnitario)) return;

            precio = cantidad * precioUnitario;

            if (precio == 0m) txtPrecio3.Clear();

            else txtPrecio3.Text = precio.ToString("C");

            subtotal += precio;

 

            if (ValidaFila(txtDescripcion4, txtCantidad4, txtPrecioUnitario4,

                out cantidad, out precioUnitario)) return;

            precio = cantidad * precioUnitario;

            if (precio == 0m) txtPrecio4.Clear();

            else txtPrecio4.Text = precio.ToString("C");

            subtotal += precio;

 

            // Muestra el subtotal.

            txtSubtotal.Text = subtotal.ToString("C");

 

            // Obtiene la tasa de impuestos como una cadena.

            string IVAPorcString = txtIVAPorc.Text;

 

            // Elimina el carácter% si está presente.

            IVAPorcString = IVAPorcString.Replace("%", "");

 

            // Parsea la tasa de impuestos.

            decimal IVAPorc;

            if (!decimal.TryParse(IVAPorcString, out IVAPorc))

            {

          

                MuestraErrores(

                    "Formato no válido. el % de IVA debe ser un valor decimal.",

                    "Formato no válido", txtIVAPorc);

                return;

            }

 

            // Si la cadena original contiene el carácter%, divide entre 100.

            if (txtIVAPorc.Text.Contains("%")) IVAPorc /= 100;

 

            // Se Asegura de que la tasa de impuestos está entre 0,00 y 0,20.

            if ((IVAPorc < 0m) || (IVAPorc > 0.2m))

            {

            

                MuestraErrores(

                    "Formato no válido. El % de IVA debe estar entre  0,00 y 0,20.",

                    "Formato no válido", txtIVAPorc);

                return;

            }

 

            // En este punto tenemos todos los datos que necesitamos.

            // Calcula y muestra el impuesto sobre las ventas.

            decimal decIVA = subtotal * IVAPorc;

            txtIVATotal.Text = salesTax.ToString("C");

 

            // Calcula y muestra el total

            decimal Total = subtotal + decIVA;

            txtTotal.Text = grandTotal.ToString("C");

 

            // Muestra un mensaje de éxito  y pregunta si queremos continuar.

            if (MessageBox.Show("Orden válida. Desea Continuar?", "Continuar?",

                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)

            {

                // Si el usuario pulsa si,  sale se deshabilita pues si no, no se ve el resultado

                //Close();

            }

        }

 

        // Valida una fila. Si hay algún valor presente, todos son obligatorios.

        // Si todos los valores están presentes, establece los parámetros de salida.

        // Si hay un error, pone el foco en el

        // cuadro de texto apropiado y devuelve true.

        private bool ValidaFila(TextBox descrTextBox, TextBox cantidadTextBox,

            TextBox precioUnitarioTextBox, out int cantidad, out decimal precioUnitario)

        {

            // Asume que hay 0.

            cantidad = 0;

            precioUnitario = 0;

 

            // Si no hay valores presentes, la fila está bien.

            if ((descrTextBox.Text == "") &&

                (cantidadTextBox.Text == "") &&

                (precioUnitarioTextBox.Text == ""))

                return false;

 

            // Se asegura de que estén todos los valores rellenos.

            if (ValidaTextBoxObligatoria(descrTextBox, "Descripcion")) return true;

            if (ValidaTextBoxObligatoria(cantidadTextBox, "Cantidad")) return true;

            if (ValidaTextBoxObligatoria(precioUnitarioTextBox, "Precio Unitario")) return true;

 

            // Todos los valores están presentes.

            // Intenta analizar (parsear)  la cantidad.

            if (!int.TryParse(cantidadTextBox.Text, out cantidad))

            {

        

                MuestraErrores(

                    "Cantidad no válida. La cantidad debe ser un valor entero.",

                    "Cantidad no válida", cantidadTextBox);

                return true;

            }

 

            // S easegura de que la cantidad esté entre 1 y 100.

            if ((cantidad < 1) || (cantidad > 100))

            {

           

                MuestraErrores(

                    "Cantidad no válida. La cantidad debe estar entre  1 y 100.",

                    "Cantidad no válida", cantidadTextBox);

                return true;

            }

 

            // Parsea el precio Unitario.

            if (!decimal.TryParse(precioUnitarioTextBox.Text, NumberStyles.Currency,

                null, out precioUnitario))

            {

 

                MuestraErrores(

                    "Precio Unitario no válido. El precio unitario debe ser un valor de tipo moneda.",

                    "Precio Unitario no válido", precioUnitarioTextBox);

                return true;

            }

 

            // Se asegura de que el precio Unitario esta entre  0,01 € y  100.000,00 €.

            if ((precioUnitario < 0.01m) || (precioUnitario > 100000.00m))

            {

              

                MuestraErrores(

                    "Precio Unitario no válido. El precio unitario debe estar entre 0,01 € and 100.000,00 €",

                    "Precio Unitario no válido", precioUnitarioTextBox);

                return true;

            }

 

            // Si llegamos aquí, tendremos todos nuestros datos.

            // Devuelve falso para indicar que no hay ningún error en esta línea.

            return false;

        }

 

        // Este TextBox es obligatorio. Si está en blanco,

        // se lo indica al usuario, le pone el foco y devuelve true

        private bool ValidaTextBoxObligatoria(TextBox txt, string name)

        {

            // Si el TextBox no está en blanco, devuelve falso.

            if (txt.Text != "") return false;

 

            // El TextBox está en blanco.

            MuestraErrores(name + " es requerido", "Valor faltante", txt);

            return true;

        }

 

        // Informa al usuario que hay un error, selecciona todos los

        // textos del TextBox y establece el foco en TextBox.

        private void MuestraErrores(string message, string title, TextBox txt)

        {

            MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);

            txt.SelectAll();

            txt.Focus();

        }

        //sale

        private void button1_Click(object sender, EventArgs e)

        {

            Close();

        }

    }

}

 

sábado, 14 de mayo de 2022

Arquitectura de datos. Identificación de las partes interesadas (Stakeholders)

Veremos cómo identificar a las partes interesadas que deben ser parte de un programa de gobierno de datos. El equipo lo es todo en una estrategia de gobierno de datos. Sus partes interesadas dan forma a la estrategia y las empresas quieren comprender quiénes se verán afectados por una nueva actividad porque eso ayudará a aplicar la geografía y los límites en torno a la política. Debemos alinear nuestras necesidades con las habilidades necesarias. Esto significa elegir a las personas que trabajarán con la tecnología o quienes realmente utilizarán los datos. 

Los técnicos no entenderán las necesidades del negocio y los analistas no entenderán la tecnología. La idea es darle a cada bando suficiente información para que entiendan que están haciendo. Para ello, el liderazgo es clave para cualquier grupo de partes interesadas (Stakeholders). Los líderes deben ser identificados y empoderados. No nos referimos a gerentes o superiores sino a personas que comprenden y llevan a otros con ellos. En términos generales, hay tres grupos principales que deseamos identificar como partes interesadas clave. 

Identificación de las partes interesadas (Stakeholders)

Primero, los creadores de datos, los técnicos. Luego están los usuarios de los datos, las gente que utilizará los datos. Y tercero los creadores de reglas de datos, aquellos que tienen interés en las políticas y procedimientos. 

Una vez identificadas estas tres partes interesadas, hay distintas categorías en cada una de ellas. Y también existe la necesidad de una combinación de personas técnicas y no técnicas interesadas en la gestión de datos. Tendremos guardianes. Los que guardan y mantienen los datos, ya sean técnicos o usuarios, específicamente los guardianes de datos y los analistas de negocios. También hay partes interesadas en las actividades de recopilación de datos y gobernanza que se expanden a la alta dirección, la Junta Directiva. Los oficiales legales y de cumplimiento ciertamente tienen un gran interés en la recopilación de datos. Y luego están los proveedores o vendedores e incluso nuestros clientes. 

Todo esto nos ayudará a evaluar a las partes interesadas y nuestro grupo estará directamente involucrado en la gobernanza y el manejo de datos. También tenemos a los que estarán interesados y afectados por estas actividades. Una vez que hayamos identificado a todas las partes interesadas, hay un modelo que podemos utilizar para incorporarlas y obtener su apoyo. Generalmente, debemos tener tres argumentos para  que se adapten a nuestras necesidades. 

Primero, debemos tener una respuesta a la pregunta, ¿qué gano yo? Cada tipo de actor tendrá diferentes intereses y necesidades. Por lo tanto, determinaremos cómo se beneficiará un ingeniero de software con estas actividades. Debemos asegurarnos de que sepan cómo la estrategia de gobernanza les facilitará la vida. Si es alguien en ventas o marketing, no debería ser difícil argumentar cómo tendrán información mejor y más útil para dirigirse a los clientes. Identificaremos el potencial y las oportunidades y haremos que estas actividades sean valiosas. La gobernanza puede parecer aburrida a primera vista, pero es una necesidad absoluta. Lo mismo ocurre con los datos. Encontrar el valor tanto en la recopilación de datos como en una política de gobernanza arrojará luz sobre nuevas oportunidades de negocio.

Tecnologías en la nube y gobernanza de datos

A continuación veremos  las formas en que las tecnologías en la nube afectan la gobernanza de datos.

Un gran beneficio de la nube es que es rentable para las empresas que no quieran mantener sus propios equipos para el almacenamiento de datos. Además ofrece un tiempo de actividad del 100%, una necesidad fundamental para cualquier empresa. Y eso se debe en parte a su escalabilidad, con nuevos servidores virtuales en marcha cuando aumenta el tráfico. La nube es una excelente manera para las empresas que desean descargarse de sus operaciones de datos y concentrarse en el negocio. Los proveedores de la nube ofrecen experiencia y análisis integrados, lo que hace que sea una transición fácil incluso para las organizaciones más grandes.

Hay consideraciones importantes para las empresas que utilizan la nube o que están considerando un salto a la nube. 

Intimidad

Mantener la privacidad de la información de una empresa y la información de los clientes que recopilan es de suma importancia por todo tipo de razones. La seguridad también va de la mano con la privacidad porque para mantener la privacidad de la información, debemos protegerla de filtraciones y piratas informáticos. 

Confíanza 

En el proveedor de la nube, en sus procesos y políticas y, en última instancia, que nuestros clientes confíen en nuestra empresa. Si perdemos esa confianza, es muy difícil recuperarla. 

Gobernanza

La gobernanza en una nube es diferente. Por ejemplo, los datos que solíamos guardar en el sótano en un servidor,  ahora dicho servidor puede encontrarse en San Francisco, Londres o Berlín. 

Tener los datos en la nube elimina la mayor parte de los errores generados por el usuario, pues en este caso es el proveedor de la nube quien garantiza la privacidad y la seguridad de estos.

Generalmente hay dos tipos de nubes. Nubes privadas, que se ubican in situ incluso si la ubicación física no está emparejada con la sede de una empresa. Nubes públicas, que son soluciones alojadas por proveedores que tienen centros de datos ubicados en múltiples ubicaciones alrededor del mundo. Y nubes híbridas, que son una mezcla de las dos primeras. Y así, la gobernanza de datos, en lo que respecta a la nube, puede ser un objetivo en constante movimiento porque, además de preocuparnos por los errores de usuario, debemos comprender dónde se encuentran físicamente nuestros datos porque pueden estar sujetos a leyes en diferentes partes de el mundo.

Para establecer un gobierno adecuado para los datos basados en la nube, nuestra empresa debe cultivar un cierto nivel de experiencia.  Para utilizar eficazmente la nube, las empresas deben desarrollar una estrategia empresarial para la nube.


sábado, 7 de mayo de 2022

Curso avanzado de C#. Empaquetado y desempaquetado de tipos de valor (Boxing and Unboxing)

El empaquetado (boxing) es el proceso de convertir un tipo de valor como un int o booleano en un objeto o una interfaz que sea compatible con el tipo de valor. El desempaquetado (unboxing) es el proceso de convertir un valor empaquetado en su valor original. Por ejemplo, el siguiente código crea una variable entera y luego crea un objeto que hace referencia a su valor: 

// Declaramos  e inicializamos el entero i. 

int i = 10; 

// Empaquetamos i

object iObject = i; 

Después de ejecutar este código, la variable iObject es un objeto que hace referencia al valor 10.

Curso avanzado de C#. Empaquetado y desempaquetado de tipos de valor (Boxing and Unboxing)


El Empaquetamiento (boxing) y el desempaqutamiento (unboxing) consumen mucho más tiempo que simplemente asignar un tipo de variable de valor igual a otro, por lo que debemos evitar empaquetar y desempaquetar siempre que sea posible. 

Por lo general, el empaquetamiento y el desempaquetamiento se realizan  automáticamente sin tomar ninguna acción especial. Esto sucede cuando invocamos un método que espera un objeto como parámetro pero le pasamos un valor. Por ejemplo : 

int i = 2465; 

Console.WriteLine (string.Format ("i es: {0}", i)); 

La versión del método Format de la clase string que se utiliza aquí toma como parámetro una cadena con formato y una secuencia de objetos que vamos a imprimir. El método examina los objetos y los imprime adecuadamente. 

El código pasa la variable de valor i al método Format. Ese método espera un objeto como parámetro, por lo que el programa automáticamente empaqueta el valor.

Idealmente, podríamos evitar esto haciendo que el método Format tome un int como parámetro en lugar de un objeto, pero entonces, ¿qué haríamos si queremos pasar al método un objeto double, DateTime o Person? Incluso si creamos versiones sobrecargadas del método Format para manejar todos los tipos de datos básicos (int, double, string, DateTime, bool, etc.), no podríamos manejar todas las combinaciones posibles que pueden ocurrir en una lista de parámetros. 

La solución es hacer que Format tome objetos no específicos como parámetros y luego utilice la reflexión para saber cómo imprimirlos.

De forma similar, podemos utilizar objetos no específicos para los parámetros de los métodos que escribamos y luego usar la reflexión para averiguar qué hacer con los objetos. Por lo general, obtendremos mejor rendimiento si podemos utilizar un tipo de datos, una interfaz o un tipo genérico más específico para los parámetros. Incluso si estamos dispuestos a vivir con el éxito de la actuación del empaquetamiento y el desempaquetamiento tienen un efecto secundario sutil que puede llevar a un código confuso. 

Considerar nuevamente el siguiente código: 

// Declara e inicializa el entero i. 

int i = 10;

 // empaqueta i.

objeto iObject = i; 

Después de ejecuta este código, la variable iObject es un objeto que hace referencia al valor 10, pero no es el mismo valor 10 almacenado en la variable i. Eso significa que si el código cambia uno de los valores, el otro no cambia también. Por ejemplo, el siguiente código, agrega algunas declaraciones a la versión anterior: 

// Declara e inicializa el entero i. 

int i = 10;

 // empaqueta i.

objeto iObject = i; 

// Cambia los valores. 

i = 1;

iObject = 2;

// Muestra los valores. 

Console.WriteLine (i); 

Console.WriteLine (iObject); 

Este código crea una variable entera i y la empaqueta en la variable iObject. Luego establece i igual a 1 e iObject igual a 2. La salida que se obtiene es:

1

2

La variable iObject parece referirse a la variable i pero en realidad son dos valores separados. Por cierto, el método Console.WriteLine tiene muchas versiones sobrecargadas, incluida una que toma un int como parámetro, por lo que la primera declaración WriteLine en el código anterior no requiere boxing o unboxing. La segunda declaración WriteLine debe desempaquetar iObject para obtener su valor actual 2. 

La moraleja de la historia es que, si es posible, debemos evitar el empaquetado y  desempaquetado al no almacenar referencias a tipos de valores en los objetos. Si el programa empaqueta y desempaqueta automáticamente un valor como lo hace el método string.Format, generalmente no hay mucho que pueda hacer al respecto. Finalmente, no debemos declarar parámetros de método u otras variables para tener un objeto de tipo no específicado a menos que no tengamos otra opción. 

Garantizar la interoperabilidad de código no administrado 

La interoperabilidad, permite que un programa de C# utilice clases proporcionadas por código no administrado que no se haya escrito bajo el control de Common Language Runtime (CLR), que es el entorno de ejecución que ejecuta los programas C#. Los componentes ActiveX y la API de Win32 son ejemplos de código no administrado que se puede invocar desde un programa de C#. 

Las dos técnicas más comunes para permitir que los programas administrados utilicen código no administrado son COM Interop y Platform invoke (P/invoke). COM Interop se analiza brevemente en la siguiente sección. Esta sección trata sobre P/invoke. 

Para utilizar P/invoke y acceder a un recurso no administrado, como una llamada a la API, un programa primero incluye un atributo DllImport para definir los métodos no administrados que utilizará el programa administrado. El atributo DllImport es parte del espacio de nombres System.Runtime.InteropServices, por lo que muchos programas agregan ese espacio de nombres en una declaración de uso para facilitar el uso del atributo. El atributo DllImport toma parámetros que informan al programa administrado sobre un método no administrado. Los parámetros indican cosas como la DLL que contiene el método, el juego de caracteres utilizado por el método (Unicode o ANSI) y el punto de entrada en la DLL utilizada por el método. (Si omitimos esto, el valor predeterminado es el nombre del método).

El programa aplica la declaración de método con el el atributo Static extern. La declaración incluye los parámetros que requiere el método y define el tipo de retorno. Esta declaración debe estar dentro de una clase, por ejemplo en la clase que contiene el código que utiliza el método. 

Por ejemplo, el siguiente código muestra dónde se coloca la instrucción using y el atributo DllImport en el programa de ejemplo ShortPathNames (que se describe brevemente con mayor detalle). Se resalta la instrucción DllImport

Using System;

Using System.Collections.Generic;

 ... Otras declaraciones estandard "using"...

Using System.Runtime.InteropServices;

 

Namespace ShortPathNames

{

      Partial Public Class Form1 :  Form

      {

            Public Form1()

            {

                  InitializeComponent();

            }

            [DllImport("kernel32.dll", CharSet = CharSet.Auto,

            SetLastError = true)]

            Static extern uint GetShortPathName(String lpszLongPath,

            Char[] lpszShortPath, int cchBuffer);

            ... Continuamos con nuestro código ...

      }

La instrucción DllImport indica que el método está en la biblioteca kernel32.dll, que el programa debe determinar automáticamente si quiere utilizar el juego de caracteres Unicode o ANSI y que el método debe llamar a SetLastError si hay un problema. Si hay un error, el programa puede usar GetLastWin32Error para ver qué salió mal. 

La declaración del método indica que el programa utilizará el método GetShortPathName, que convierte una ruta completa a un archivo en una ruta corta que Windows puede reconocer. (Si el método usa el juego de caracteres Unicode, el nombre del método generalmente termina con una "W" para "caracteres anchos" como en GetShortPathNameW). Este método devuelve un uint y toma como parámetros una cadena, una matriz de caracteres e int. 

Nota 

A menudo, los prefijos en los nombres de los parámetros nos dan pistas sobre los propósitos de esos parámetros. En este ejemplo, lpsz significa "puntero largo a una cadena que termina en cero" y cch significa "recuento de caracteres". Si leemos la ayuda en línea para la función API GetShortPathName, encontraremos que esos prefijos tienen sentido.

El primer parámetro es la ruta del archivo que deseamos convertir en una ruta corta. Cuando llamamos al método, P/Invoke lo convierte automáticamente en una cadena terminada en nulo. El segundo parámetro debe ser un búfer preasignado donde GetShortPathName pueda almacenar sus resultados. El tercer parámetro proporciona la longitud del búfer que asignamos, por lo que GetShortPathName sabe cuánto espacio tenemos para trabajar. 

El método devuelve un uint que indica la longitud de la cadena que el método depositó en el búfer lpszLongPath

Podemos ver la sintaxis de esta instrucción DllImport mirando la firma del método aquí

En http://www.pinvoke.net podemos ver declaraciones de DllImport para una gran cantidad de funciones de la API de Win32. Cuando necesitemos utilizar una función de la API de Win32, este es un buen sitio para comenzar. 

Una vez declarado el método, el programa puede comenzar a utilizarlo. A continuación, un ejemplo de utilización del método ShortPathNames.

// Obtiene el nombre de archivo largo.

String NombreLargo = txtArchivo.Text;

// Asigna un búfer para contener el resultado.

Char[] buffer = New Char[1024];

      Long length = GetShortPathName(

      NombreLargo, Buffer,

Buffer.Length);

// Obtiene el nombre corto.

String NombreCorto = New String(Buffer);

txtNombreCorto.Text = NombreCorto.Substring(0, (Int())length);

Este código recibe una ruta larga de archivo introducida por el usuario en el control txtArchivo y asigna un búfer de 1024 caracteres para mantener la ruta corta. Después llama al método GetShortPathName, al que le pasa la ruta larga de archivo el búfer y la longitud del búfer. Una vez que el método regresa, el programa usa el búfer para inicializar una nueva cadena. Utiliza el método Substring y la longitud devuelta por GetShortPathName para truncar la cadena a su longitud adecuada y muestra el resultado. 

En general, el tipo de instrucción DllImport que hemos visto es lo suficiente para hacer el trabajo. Pero si necesitamos más control para convertir valores entre código administrado y no administrado, podemos agregar el atributo MarshalAs a los parámetros del método o al valor de retorno. El siguiente código muestra una nueva versión de la instrucción DllImport para el método GetShortPathName que utiliza atributos MarshalAs

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]

Static extern uint GetShortPathName(

      [MarshalAs(UnmanagedType.LPTStr)] string lpszLongPath,

      [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszShortPath,

      uint cchBuffer);


El primer atributo MarshalAs indica que el primer parámetro es un tipo de datos LPTStr en el código no administrado y debe tratarse como una cadena en el código administrado. El segundo atributo MarshalAs indica que el segundo parámetro es un tipo de datos LPTStr en el código no administrado y debe tratarse como un StringBuilder en el código administrado. Por supuesto, si utilizamos esta declaración, necesitaremos cambiar nuestro código para usar un StringBuilder para un búfer en lugar de una matriz de char.