C#: Implementar la Interface ICloneable

No será la primera vez que me he encontrado en la tesitura de tener que duplicar todos los datos de una clase. Quizás, el ejemplo más claro lo encontremos cuando tenemos un grid creado y el usuario te sugiere la posibilidad de crear una nueva función que permita copiar una fila ya existente, con todos sus datos. De esta manera, modificando sólo los datos que varían, la creación de la nueva fila es mucho más sencilla. Reconozco que en alguna ocasión he llegado a implementar la Interface ICloneable de un modo tan rupestre como es la creación de una nueva clase y, propiedad a propiedad, ir haciendo una copia de los valores de la misma. Si hablamos de clases con pocas propiedades y con la premisa que nos caracteriza a casi todos los programadores (¡hay prisa en entregar!), esta acción, sin llegar a ser muy ortodoxa, tiene una justificación, pero cuando la clase ya tiene un número considerable de propiedades o es una clase que varía constantemente según las “exigencias del guión”, no dejará de ser un “coladero de errores”.

Vamos a intentar, en esta ocasión, aprender un poco más de la implementación de la interface ICloneable con el objetivo de poder obtener un rendimiento mejorado.

De hecho, si nuestro objetivo es obtener una copia superficial de una clase, no haría falta la implementación de la interface mencionada, puesto que la clase object ya contiene un método protegido que nos proporciona una “copia” de la clase. Por ejemplo:

using System;

namespace JnSoftware.Test
{
    class Coche
    {
        /// <summary>Obtiene la matrícula de un coche</summary>
        public string Matricula { get; private set; }

        /// <summary>Constructor de la clase.</summary>
        /// <param name="matricula">Matrícula del vehículo</param>
        public Coche(string matricula)
        {
            Matricula = matricula;
        }

        /// <summary>
        /// Ejecución del ejemplo
        /// </summary>
        public static void Main()
        {
            Coche c1 = new Coche("0000AAA");
            Coche c2 = (Coche)c1.MemberwiseClone();

            Console.WriteLine(c1.Matricula);
            Console.WriteLine(c2.Matricula);

            Console.ReadKey();
        }

    }
}

Debemos fijarnos en el método MemberwiseClone(), el cual realiza la función que andábamos buscando. ¿Dónde está entonces el problema?. Es muy sencillo. A la clase que estamos usando de ejemplo, vamos a añadirle algún tipo de funcionalidad extra, de tal manera que se asemeje más a un ejemplo de la vida real que no a un ejercicio básico de programación. En este caso, vamos a incluir las multas que pueda llegar a tener un coche.

Clase: Multas

using System;

namespace JnSoftware.Test
{

    /// <summary>
    /// Clase que representa la multa que tiene un coche determinado.
    /// </summary>
    internal class Multa
    {
        public DateTime Fecha { get; set; }
        public decimal Importe { get; set; }

    }
}

Clase: Coche

using System;
using System.Collections.Generic;
using System.Linq;

namespace JnSoftware.Test
{
    public class Coche
    {
        /// <summary>Obtiene la matrícula de un coche</summary>
        public string Matricula { get; private set; }

        private List<Multa> listaMultas;

        /// <summary>Constructor de la clase.</summary>
        /// <param name="matricula">Matrícula del vehículo</param>
        public Coche(string matricula)
        {
            listaMultas = new List<Multa>();
            Matricula = matricula;
        }

        /// <summary>
        /// Añade una multa
        /// </summary>
        public void AddMulta(DateTime fechaMulta, decimal importe)
        {
            listaMultas.Add(new Multa() { Fecha = fechaMulta, Importe = importe });
        }

        /// <summary>
        /// Obtiene el total de multas de un coche
        /// </summary>
        public decimal GetImporteMultas()
        {
            return listaMultas.Sum(m => m.Importe);
        }

        /// <summary>
        /// Ejecución del ejemplo
        /// </summary>
        public static void Main()
        {
            Coche c1 = new Coche("0000AAA");
            c1.AddMulta(DateTime.Today, 1000);
            
            Coche c2 = (Coche)c1.MemberwiseClone();
            c2.Matricula = "1111BBB";
            c2.AddMulta(DateTime.Today, 1000);


            Console.WriteLine("Coche: {0}tMultas:{1}",c1.Matricula,c1.GetImporteMultas());
            Console.WriteLine("Coche: {0}tMultas:{1}", c2.Matricula, c2.GetImporteMultas());

            Console.ReadKey();
        }
    }
}

Si ejecutamos el ejemplo nos llevaremos una sorpresa. Hemos incluido una multa en el coche c1, con un importe de 1000. En la instancia que hemos duplicado, vemos que la segunda multa, también de 1000 se ha añadido al coche c2, en cambio, a la hora de obtener el resultado del importe de las multas que han tenido ambos vehículos, vemos que los dos tienen 2000 como suma del importe de sus multas. ¿Qué ha pasado?; sencillamente, que no hemos leído en profundidad la ayuda del método MemberwiseClone(), sobre todo, cuando hablaba de una “copia superficial”. Así pues, ¿qué es una “copia superficial”?:

Si un campo es de un tipo de valor, se realiza una copia bit a bit de él.Si un campo es un tipo de referencia, se copia la referencia pero no el objeto al que se hace referencia; por consiguiente, el objeto original y su copia hacen referencia al mismo objeto

    Al haber utilizado, por ejemplo, una List<Multas>, que es un objeto por referencia, vemos que no se ha duplicado su contenido sino que se ha hecho uso de la referencia. Es por eso que el resultado de c1.GetImporteMultas() es el mismo que c2.GetImporteMultas(), aunque la multa de c2 se haya introducido después de haber hecho el “duplicado”.

    Ahora ya conocemos el problema. Vamos a ver cómo podemos resolverlo. Una opción pasaría por hacer una clonación superficial de la clase y, para aquellos elementos que precisen de una intervención manual, podemos, por ejemplo, crear una serie de extensores que permitan la creación de elementos nuevos. En el caso del campo listaMultas, por ejemplo, podríamos crear un método extensor de la clase List<T>:

    using System.Collections.Generic;
    using System.Linq;
    
    namespace JnSoftware.Extensions
    {
        public static class MetodosExtensores
        {
            
            /// <summary>
            /// Clona una clase List
            /// </summary>
            public static List<T> Clone<T>(this List<T> lista) 
            {
                return lista.ToList();
            }
    
        }
    }
     

    De esta manera, una vez referenciado el namespace JnSoftware.Extensions en la clase Coche, bastará con modificar el método Clone de la misma:

    public object Clone()
    {
        // Obtenemos una copia superficial de la clase
        Coche nuevo = (Coche)this.MemberwiseClone();
       
        // Clonación manual de campos
        nuevo.listaMultas = this.listaMultas.Clone();
        return nuevo;
    }

    Claro, visto así, no es que sea una clonación muy “profunda” de los elementos de la clase. Cuanto menos, nada automática ni generalizable, sobre todo si nuestra aplicación hace uso de Colecciones, Listas, Diccionarios y demás contenedores por doquier.

    A partir de ahora, reconozco que hubiese sido incapaz de ver una solución más o menos práctica del problema, sobre todo, cuando mis derroteros iban rascando las clases del namespace System.Reflection, con las propiedades y métodos de la clase Type. Menos mal que San Google nos ayuda en nuestros momentos de extrema depresión. Así, una visita a la página http://es.debugmodeon.com/articulo/clonar-objetos-de-estructura-compleja me ha permitido ver cómo cualquier clase Serializable puede clonarse serializando primero en memoria y deserializando después. Por tanto, es cuestión de crear una clase auxiliar que nos permita tener un método útil para poder clonar clases con estructuras complejas:

    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    
    namespace JnSoftware
    {
        public static class Utiles
        {
            /// <summary>
            /// Permite una clonación en profundidad de origen
            /// </summary>
            /// <param name="origen">Objeto serializable</param>
            /// <exception cref="ArgumentExcepcion">
            /// Se produce cuando el objeto no es serializable.
            /// </exception>
            /// <remarks>Extraido de 
            /// http://es.debugmodeon.com/articulo/clonar-objetos-de-estructura-compleja
            /// </remarks>
            public static T Copia<T>(T origen)
            {
                // Verificamos que sea serializable antes de hacer la copia            
                if (!typeof(T).IsSerializable)
                    throw new ArgumentException("La clase " + typeof(T).ToString() + " no es serializable");
                
                // En caso de ser nulo el objeto, se devuelve tal cual
                if (Object.ReferenceEquals(origen, null))
                    return default(T);
                
                //Creamos un stream en memoria            
                IFormatter formatter = new BinaryFormatter();
                Stream stream = new MemoryStream();
                using (stream)
                {
                    try
                    {
                        formatter.Serialize(stream, origen);
                        stream.Seek(0, SeekOrigin.Begin);
                        //Deserializamos la porcón de memoria en el nuevo objeto                
                        return (T)formatter.Deserialize(stream);
                    }
                    catch (SerializationException ex)
                    { throw new ArgumentException(ex.Message, ex); }
                    catch { throw; }
                }
            }
        }
    }
     

    Eso sí, para todas aquellas clases que queramos Clonar con este método, deberemos añadir el atributo Serializable:

    [Serializable()]
    public class Coche : ICloneable

    Ahora, el método Clone de cualquiera de las clases que queramos “clonar”, será tan sencillo de escribir como el método MemberwiseClone()  que usamos al principio:

    public object Clone()
    {
        return JnSoftware.Utiles.Copia(this);
    }

    Confiemos que ya no tenga que ir copiando más las propiedades de una clase…

    ASP.Net: Crear un control de servidor. Ejemplo: JnTextBoxNif

    En el post anterior, Validar CIF, NIF, NIE me comprometí a explicar cómo hacer uso de la clase JnSoftware.Validaciones.NumeroNif para validar un documento introducido en una página ASP.NET. Debo confesar que el motivo inicial era crear un control de usuario en el que se validase dicho documento embebido dentro de un UpdatePanel. Como hasta el momento no había creado nunca un control de servidor, reconozco que ha podido más la curiosidad que otra cosa y al final, después de algún que otro problema, he creado un control de servidor que permite validar y normalizar un documento, sea CIF, NIF o NIE. De este modo, según mi parecer, nos encontramos con un mayor nivel de reusabilidad.

    Antes de nada me gustaría comentar que en las aplicaciones que creo siempre dejo que se introduzca cualquier número en el campo NIF. Sencillamente, lo que hago es que, cuando no es un documento validado lo resalto con un color rojo de fondo que traspasa al usuario la responsabilidad de aceptar o no la entrada.

    Pongámonos manos a la obra. Lo primero que deberemos crear es un proyecto que incluya los controles de servidor. En el caso que nos conlleva ahora sólo introduciremos el control JnTextBoxNif, pero ahora que empiezo a verle la utilidad a este tipo de controles, seguro que no será el único.

    image

    Automáticamente, VisualStudio crea, dentro del proyecto, una clase ServerControl1.cs con la estructura básica. Yo, lo que suelo hacer es eliminar este control y crear uno nuevo. Para ello, le indico que quiero crear un nuevo Control de Servidor.

    image

    No debemos olvidar incluir un par de referencias en el proyecto, por un lado, la referencia a la DLL creada en el post anterior que incluirá la validación del número CIF, NIF, NIE (JnSoftware.Validaciones) y, por otro, como haremos uso de un control UpdatePanel para que se actualice sólo el contenido del control de usuario a la librería System.Web.Extensions, que incluye, entre otros, dicho UpdatePanel.

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using JnSoftware.Validaciones;
    
    /* Asigna el nombre automáticamente de la etiqueta del control */
    [assembly: TagPrefix("JnSoftware.WebControls", "JnControl")]
    
    namespace JnSoftware.WebControls
    {
        [ToolboxData("<{0}:JnTextBoxNif runat=server></{0}:JnTextBoxNif>")]
        [Designer("JnSoftware.WebControls.JnTextBoxNifControlDesigner, JnSoftware.WebControls")]
        [Description("Permite la validación de un NIF,CIF,NIE")]
        [DefaultProperty("Text")]
        public class JnTextBoxNif : WebControl
        {
    
            #region Campos
            // Texbox sobre el que se validará el documento
            TextBox txtNif = new TextBox();
    
            // UpdatePanel para que se actualice sólo el control
            UpdatePanel updatePanel = new UpdatePanel();
            #endregion
    
            
            #region Control en modo de diseño
    
            /* En modo de diseño, se mostrará un Input de HTML */
            private string renderHtml = "<input type="text"  value="{0}"/>";
            
            internal string RenderHtml
            {
                get { return string.Format(renderHtml, txtNif.ID); }
            }
            
            #endregion
    
    
            [Bindable(true), Category("Appearence")]
            public string Text
            {
                get { return txtNif.Text; }
                set { txtNif.Text = value; compruebaNif(); }
            }
    
    
            protected override void OnLoad(EventArgs e)
            {
                controlesPanel();
                base.OnLoad(e);
            }
    
            
            /// <summary>
            /// Carga los elementos del control 
            /// </summary>
            private void controlesPanel()
            {
                // Textbox sobre el que se realizará la validación
                txtNif.ID = ID + "_txtNif";
                txtNif.AutoPostBack = true;
                txtNif.TextChanged += new EventHandler(txtNif_TextChanged);
    
                // UpdatePanel
                Control container = updatePanel.ContentTemplateContainer;
                
                 // Si no se establece el modo de actualización del UpdatePanel 
                 // como Conditional, al actualizar un updatePanel en la página, 
                 // se actualizan todos 
                updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
                updatePanel.RenderMode = UpdatePanelRenderMode.Inline;
    
                // Se añaden los controles
                container.Controls.Add(txtNif);
                base.Controls.Add(updatePanel);
            }
    
    
    
            void txtNif_TextChanged(object sender, EventArgs e)
            {
                compruebaNif();
            }
    
    
            #region Cálculos internos
    
            /// <summary>
            /// Comprueba que el Nif introducido sea correcto.
            /// </summary>
            private void compruebaNif()
            {
                string nif = txtNif.Text;
                try
                {
                    if (!string.IsNullOrEmpty(nif))
                    {
                        NumeroNif numero = NumeroNif.CompruebaNif(nif);
                        if (!numero.EsCorrecto)
                            throw new ApplicationException("Número no válido");
                        txtNif.Text = numero.ToString();
                    }
                    formatoTextBox(true);
                }
                catch
                {
                    formatoTextBox(false);
                }
            }
    
    
            /// <summary>
            /// Aplica un formato personalizado al TextBox en base 
            /// al resultado de la validación del documento
            /// </summary>
            /// <param name="validado">True si es un documento correcto</param>
            private void formatoTextBox(bool validado)
            {
                Color colorFondo = Color.Empty;
                if (!validado)
                    colorFondo = Color.Red;
                txtNif.BackColor = colorFondo;
            }
    
            #endregion
        }
    }
     

    Aunque he incluido la clase una vez finalizada, se puede apreciar que no reviste gran dificultad, puesto que son pocos los métodos que realmente tienen una gran trascendencia en el conjunto.

    • controlesPanel, que realiza la carga de los elementos que se incluirán dentro del UpdatePanel (en el ejemplo tan sólo existe el TextBox que nos permitirá introducir y validar el NIF, CIF, NIE).
    • compruebaNif, que es el método responsable de comprobar el documento introducido y, gracias a formatoTextBox, de aplicar un formato destacado en caso de no ser válido.

    Ahora bien, una vez finalizada la clase, ya la veo hasta bonita, pero reconozco que me ha costado un buen rato descubrir los puntos siguientes:

    • Si creaba, dentro de una misma página varios controles JnTextBoxNif, en el momento que actualizaba uno, se actualizaban también todos los demás. Esto es consecuencia del modo de actualización de los diferentes UpdatePanel (uno por cada control). El modo de solventar este problema es cambiar el modo de actualización del UpdatePanel a Conditional.
    • Quizás sea una tontería, pero personalmente suele molestarme mucho que, cuando añado controles personales en una página, si me limito a arrastrar el componente desde la pantalla de diseño, VisualStudio le asigna un prefijo automático del tipo TagPrefix=”cc1”. Solvento este problema con la etiqueta [assembly] para que todos los controles tengan el mismo prefijo en la página.
    • Otra sorpresa: aunque el control funciona perfectamente en tiempo de ejecución, resulta que en vista de diseño no se muestra absolutamente nada. Para “mostrar algo” cuando esté trabajando en el modo de diseño me fijo en la clase System.Web.UI.Design.ControlDesigner, que me obliga a añadir una referencia nueva, System.Design. Una vez añadida dicha referencia, heredo de la clase ControlDesigner para que se muestre en tiempo de diseño un elemento similar al del TextBox (un Input de HTML, por ejemplo).
    using System;
    
    namespace JnSoftware.WebControls
    {
        class JnTextBoxNifControlDesigner : System.Web.UI.Design.ControlDesigner
        {
            public override string GetDesignTimeHtml()
            {
                JnTextBoxNif t = (JnTextBoxNif)Component;
                return t.RenderHtml;
            }
        }
    }

     

    Cuando ya tenemos el control diseñado es hora de probarlo, así que creo un nuevo proyecto Web dentro de la misma solución y le añado la referencia correspondiente del proyecto JnSoftware.WebControls. Aunque añadir un nuevo control es tan sólo arrastrar el control que nos encontramos en el cuadro de herramientas, dejo constancia del código de la página puesto que así, evito olvidarme de recordar que debemos añadir un ScriptManager en la página para que funcione correctamente el UpdatePanel:

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestNif.aspx.cs" Inherits="WebPresentacion.TestNif" %>
    <%@ Register Assembly="JnSoftware.WebControls" Namespace="JnSoftware.WebControls" TagPrefix="JnControl" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <asp:ScriptManager ID="sm" runat="server" />
        <div>
            NIF 1: <JnControl:JnTextBoxNif ID="JnTextBoxNif1" runat="server" /><br />
            NIF 2: <JnControl:JnTextBoxNif ID="JnTextBoxNif2" runat="server" /><br />
        </div>
        </form>
    </body>
    </html>

    C#. Validar CIF, NIF, NIE

    Imagino que, al igual que todos los que alguna vez hemos empezado en esto del desarrollo de aplicaciones, nuestro ego nos ha jugado alguna que otra mala pasada. Ya desde mis inicios de la mano de Access o de Visual Basic 3.0, veía con bastante desgana cómo se iban introduciendo CIFs del tipo A00000000 o A-00000000, o A-00.000.000; matrículas con formatos dispares: GGG0000 ó 0000GGG. Comento y quizás resalto “desgana”, puesto que es uno de los puntos menos valorados tanto por desarrolladores como por clientes. Si total, sólo vale para perder el tiempo y retrasar la finalización del programa. Claro, que el problema nos lo encontramos nosotros cuando llega el momento de obtener un listado ordenado o una simple búsqueda. No recuerdo si fue la versión 5 de VB la que ya incluía la posibilidad de crear OCX que, entre otras cosas, permitía separar un conjunto de controles con el objetivo de reutilizarlos del código de la aplicación.

    El caso es que he tenido miles de peleas con los datos cuando éstos no estaban normalizados, así que un día me levanté y me puse manos a la obra con la finalidad de intentar homologar todos aquellos datos que sean susceptibles de tener un “formato igual”. En el blog anterior ya quedó alguno de estos intentos. Una de las rutinas que más me costó en su día fue el intento de poder validar, en un mismo campo, un CIF de empresa, un NIF de persona física y el Número de Identificación de Extranjería (NIE), sin tener que indicarle previamente el tipo de documento.

    Como suele pasar cada vez que se empieza un nuevo proyecto, el primer boceto contenía múltiples errores. Algunos de ellos se fueron subsanando; otros, quizás por la falta de tiempo habitual, no quedaron pulidos del todo. Así, aprovechando el haber tenido un poco de tiempo estas fiestas, he intentado pulir el código, de tal manera que sea útil para cualquiera que lo precise.

    Uno de los errores que tenía era la eliminación, en la normalización, de aquellos números que empezaban por 0 y la asignación de algún NIE cuando realmente no lo era. Lo único que he omitido de los comentarios que me hicieron en su día es un tema sobre el que anduve buscando información y no supe encontrar una solución bien documentada: los NIE están formados por una letra, ocho dígitos numéricos y el dígito de control. En su lugar, he optado por tomar la definición y la normativa publicada en Wikipedia a tal respecto.

    Dejo el código tal cuál ha quedado ya reformado:

    using System;
    using System.Text.RegularExpressions;
    
    /*
     * NumeroNif.cs
     * Servicios de validación de los números NIF
     * 
     * älskare, Ene/11
     */
    
    namespace JnSoftware.Validaciones
    {
    
        /// <summary>
        /// Representa un número. En la clase se desglosan las distintas opciones que se puedan
        /// encontrar
        /// </summary>
        public class NumeroNif
        {
            /// <summary>
            /// Tipos de Códigos.
            /// </summary>
            /// <remarks>Aunque actualmente no se utilice el término CIF, se usa en la enumeración
            /// por comodidad</remarks>
            private enum TiposCodigosEnum { NIF, NIE, CIF }
    
            // Número tal cual lo introduce el usuario
            private string numero;
            private TiposCodigosEnum tipo;
    
            /// <summary>
            /// Parte de Nif: En caso de ser un Nif intracomunitario, permite obtener el cógido del país
            /// </summary>
            public string CodigoIntracomunitario { get; internal set; }
            internal bool EsIntraComunitario { get; set; }
    
            /// <summary>
            /// Parte de Nif: Letra inicial del Nif, en caso de tenerla
            /// </summary>
            public string LetraInicial { get; internal set; }
    
            /// <summary>
            /// Parte de Nif: Bloque numérico del NIF. En el caso de un NIF de persona física,
            /// corresponderá al DNI
            /// </summary>
            public int Numero { get; internal set; }
    
            /// <summary>
            /// Parte de Nif: Dígito de control. Puede ser número o letra
            /// </summary>
            public string DigitoControl { get; internal set; }
    
            /// <summary>
            /// Valor que representa si el Nif introducido es correcto
            /// </summary>
            public bool EsCorrecto { get; internal set; }
    
            /// <summary>
            /// Cadena que representa el tipo de Nif comprobado:
            ///     - NIF : Número de identificación fiscal de persona física
            ///     - NIE : Número de identificación fiscal extranjería
            ///     - CIF : Código de identificación fiscal (Entidad jurídica)
            /// </summary>
            public string TipoNif { get { return tipo.ToString(); } }
    
            /// <summary>
            /// Constructor. Al instanciar la clase se realizan todos los cálculos
            /// </summary>
            /// <param name="numero">Cadena de 9 u 11 caracteres que contiene el DNI/NIF
            /// tal cual lo ha introducido el usuario para su verificación</param>
            private NumeroNif(string numero)
            {
                // Se eliminan los carácteres sobrantes
                numero = EliminaCaracteres(numero);
    
                // Todo en maýusculas
                numero = numero.ToUpper();
    
                // Comprobación básica de la cadena introducida por el usuario
                if (numero.Length != 9 && numero.Length != 11)
                    throw new ArgumentException("El NIF no tiene un número de caracteres válidos");
               
                this.numero = numero;
                Desglosa();
    
                switch (tipo)
                {
                    case TiposCodigosEnum.NIF:
                    case TiposCodigosEnum.NIE:
                        this.EsCorrecto = CompruebaNif();
                        break;
                    case TiposCodigosEnum.CIF:
                        this.EsCorrecto = CompruebaCif();
                        break;
                }
            }
    
            #region Preparación del número (desglose)
    
            /// <summary>
            /// Realiza un desglose del número introducido por el usuario en las propiedades
            /// de la clase
            /// </summary>
            private void Desglosa()
            {
                Int32 n;
                if (numero.Length == 11)
                {
                    // Nif Intracomunitario
                    EsIntraComunitario = true;
                    CodigoIntracomunitario = numero.Substring(0, 2);
                    LetraInicial = numero.Substring(2, 1);
                    Int32.TryParse(numero.Substring(3, 7), out n);
                    DigitoControl = numero.Substring(10, 1);
                    tipo = GetTipoDocumento(LetraInicial[0]);
                }
                else
                {
                    // Nif español
                    tipo = GetTipoDocumento(numero[0]);
                    EsIntraComunitario = false;
                    if (tipo == TiposCodigosEnum.NIF)
                    {
                        LetraInicial = string.Empty;
                        Int32.TryParse(numero.Substring(0, 8), out n);
                    }
                    else
                    {
                        LetraInicial = numero.Substring(0, 1);
                        Int32.TryParse(numero.Substring(1, 7), out  n);
                    }
                    DigitoControl = numero.Substring(8, 1);
                }
                Numero = n;
            }
    
            /// <summary>
            /// En base al primer carácter del código, se obtiene el tipo de documento que se intenta
            /// comprobar
            /// </summary>
            /// <param name="letra">Primer carácter del número pasado</param>
            /// <returns>Tipo de documento</returns>
            private TiposCodigosEnum GetTipoDocumento(char letra)
            {
                Regex regexNumeros = new Regex("[0-9]");
                if (regexNumeros.IsMatch(letra.ToString()))
                    return TiposCodigosEnum.NIF;
    
                Regex regexLetrasNIE = new Regex("[XYZ]");
                if (regexLetrasNIE.IsMatch(letra.ToString()))
                    return TiposCodigosEnum.NIE;
    
                Regex regexLetrasCIF = new Regex("[ABCDEFGHJPQRSUVNW]");
                if (regexLetrasCIF.IsMatch(letra.ToString()))
                    return TiposCodigosEnum.CIF;
    
                throw new ApplicationException("El código no es reconocible");
            }
    
    
    
            /// <summary>
            /// Eliminación de todos los carácteres no numéricos o de texto de la cadena
            /// </summary>
            /// <param name="numero">Número tal cual lo escribe el usuario</param>
            /// <returns>Cadena de 9 u 11 carácteres sin signos</returns>
            private string EliminaCaracteres(string numero)
            {
                // Todos los carácteres que no sean números o letras
                string caracteres = @"[^w]";
                Regex regex = new Regex(caracteres);
                return regex.Replace(numero, "");
            }
    
            #endregion
    
            #region Cálculos
    
            private bool CompruebaNif()
            {
                return DigitoControl == GetLetraNif();
            }
    
            /// <summary>
            /// Cálculos para la comprobación del Cif (Entidad jurídica)
            /// </summary>
            private bool CompruebaCif()
            {
                string[] letrasCodigo = { "J", "A", "B", "C", "D", "E", "F", "G", "H", "I" };
    
                string n = Numero.ToString("0000000");
                Int32 sumaPares = 0;
                Int32 sumaImpares = 0;
                Int32 sumaTotal = 0;
                Int32 i = 0;
                bool retVal = false;
    
                // Recorrido por todos los dígitos del número
                for (i = 0; i < n.Length; i++)
                {
                    Int32 aux;
                    Int32.TryParse(n[i].ToString(), out aux);
    
                    if ((i + 1) % 2 == 0)
                    {
                        // Si es una posición par, se suman los dígitos
                        sumaPares += aux;
                    }
                    else
                    {
                        // Si es una posición impar, se multiplican los dígitos por 2 
                        aux = aux * 2;
    
                        // se suman los dígitos de la suma
                        sumaImpares += SumaDigitos(aux);
                    }
                }
                // Se suman los resultados de los números pares e impares
                sumaTotal += sumaPares + sumaImpares;
    
                // Se obtiene el dígito de las unidades
                Int32 unidades = sumaTotal % 10;
    
                // Si las unidades son distintas de 0, se restan de 10
                if (unidades != 0)
                    unidades = 10 - unidades;
    
                switch (LetraInicial)
                {
                    // Sólo números
                    case "A":
                    case "B":
                    case "E":
                    case "H":
                        retVal = DigitoControl == unidades.ToString();
                        break;
    
                    // Sólo letras
                    case "K":
                    case "P":
                    case "Q":
                    case "S":
                        retVal = DigitoControl == letrasCodigo[unidades];
                        break;
    
                    default:
                        retVal = (DigitoControl == unidades.ToString())
                                || (DigitoControl == letrasCodigo[unidades]);
                        break;
                }
    
                return retVal;
    
            }
    
            /// <summary>
            /// Obtiene la suma de todos los dígitos
            /// </summary>
            /// <returns>de 23, devuelve la suma de 2 + 3</returns>
            private Int32 SumaDigitos(Int32 digitos)
            {
                string sNumero = digitos.ToString();
                Int32 suma = 0;
    
                for (Int32 i = 0; i < sNumero.Length; i++)
                {
                    Int32 aux;
                    Int32.TryParse(sNumero[i].ToString(), out aux);
                    suma += aux;
                }
                return suma;
            }
    
            /// <summary>
            /// Obtiene la letra correspondiente al Dni
            /// </summary>
            private string GetLetraNif()
            {
                int indice = Numero % 23;
                return "TRWAGMYFPDXBNJZSQVHLCKET"[indice].ToString();
            }
    
            /// <summary>
            /// Obtiene una cadena con el número de identificación completo
            /// </summary>
            public override string ToString()
            {
                string nif;
                string formato = "{0:0000000}";
    
                if (tipo == TiposCodigosEnum.CIF && LetraInicial == "")
                    formato = "{0:00000000}";
                if (tipo == TiposCodigosEnum.NIF)
                    formato = "{0:00000000}";
                
                nif = EsIntraComunitario ? CodigoIntracomunitario :
                    string.Empty + LetraInicial + string.Format(formato,Numero ) + DigitoControl;
                return nif;
            }
    
            #endregion
    
            /// <summary>
            /// Comprobación de un número de identificación fiscal español
            /// </summary>
            /// <param name="numero">Numero a analizar</param>
            /// <returns>Instancia de <see cref="NumeroNif"/> con los datos del número.
            /// Destacable la propiedad <seealso cref="NumeroNif.EsCorrecto"/>, que contiene la verificación
            /// </returns>
            public static NumeroNif CompruebaNif(string numero)
            {
                return new NumeroNif(numero);
            }
    
        }
    
    }
     

    En el siguiente post intentaré comentar cómo podemos crear un control de usuario que nos valide la entrada de un CIF/NIF/NIE correcto en ASP Net. Digo intentaré porque últimamente me estoy oxidando mucho con el tema de los controles de ASP. ¿Cuál es la razón?, muy sencillo, que cada día soy más vago y últimamente me estoy habituando demasiado a trabajar con los controles de DevExpress que, tal como lo veo, ofrecen una gran cantidad de opciones que, de trabajar con controles estándares me costarían bastante de hacer.