Como implementar el método Clone fuertemente tipificado

Para clonar un objeto existente debo implementar en éste, la interfaz ICloneable, la cual posee un único método llamado Clone que debe retornar un tipo object.


El inconveniente principal es que el retorno no es del tipo espcífico (type-safe) sinó que es de tipo objeto. Esto obliga a los clientes de cualquier clase a que deban convertir el resultado al tipo específico, con el consecuente riesgo de errores o fallas en la aplicación.


Si poseo una clase cómo la de más abajo, llamada Usuario y decido clonarla, debo implementar el método Clone(). En el caso de mi clase Usuario, además posee un atributo del tipo de otra clase, llamada Recursos. Tambien es necesario que implemente Clone en las demás clases, sólo si estoy interesado en que se deben clonar esas tambien.


public class Usuario


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            void Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}

 

}

 

public class Recursos //Implementación omitida por claridad


{


            public Recursos () {}

 

}


Al implementar ICloneable, nuestras clases quedan de la siguiente forma.


El método MemberWiseClone sólo clona internamente todos los tipos que son por valor, y no por referencia. En este caso, Recursos es una referencia en Usuario, por lo que es necesario hacer nosotros la llamada a su método clone.


public class Usuario : ICloneable


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            public Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}


           


            public object Clone()


            {


                        Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();


                        _objUsuarioTemp.recursos =  (Recursos) this.recursos.Clone();

 

                        return _objUsuarioTemp;


            }


}

 

public class Recursos : ICloneable //Implementación omitida por claridad


{

 

            public Recursos () { }

 

            public object Clone()


            {


                        return this.MemberwiseClone();


            }

 

}


El inconveniente que tiene esto es que el resultado del método Clone es un objeto y no es posible cambiar el comportamiento (en esta versión del framework – 1.1 – por lo menos).


¿Qué se puede hacer?


Creamos un método llamado Clone(), pero que no implemente la interfaz sino que haga el llamado a MemberWiseClone(), lo trasforme al tipo de dato específico y luego lo retorne. Además, implementamos la interfaz ICloneable que ejecuta el método que acabamos de crear.


Implementando estos nuevos métodos, el resultado es el siguiente:


public class Usuario : ICloneable


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            public Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}

 

            object ICloneable.Clone()


            {


                        return this.Clone();


            }


           


            public Usuario Clone()


            {


                        Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();


                        _objUsuarioTemp.recursos =  this.recursos.Clone();//No es necesario transformar

 

                        return _objUsuarioTemp;


            }


}

 

public class Recursos : ICloneable //Implementacion omitida por claridad


{

 

            public Recursos ()


            {


            }

 

            object ICloneable.Clone()


            {


                        return this.Clone();


            }

 

            public Recursos Clone()


            {


                        return (Recursos)this.MemberwiseClone();


            }

 

}


El resultado de esto es que, si se trasforma (cast) el objeto a ICloneable, se ejecuta el método de la interfaz, pero si se usa con el tipo específico (objeto tipificado), se utiliza el método público. Véamelo en el ejemplo de más abajo.


Un código de ejemplo en una aplicación de consola, quedaría así.


class Class1


{


            [STAThread]


            static void Main(string[] args)


            {


                        Usuario u1 = new Usuario(“www”, new Recursos());


                        Usuario u2;


                        u2 = u1.Clone(); // No es necesario el cast adicional.

 

                        //Si utilizamos el objeto a través de su interfaz, es necesario realizar el cast adicional.


                        u2 = (Usuario)((ICloneable)u1).Clone();


            }


}


Hemos implementado un método de clonación que retorna tipos específicos en vez de objetos, simplificando el trabajo en los clientes que utilicen estas clases, ya que no son ellos los responsables de realizar la conversión.


Patrick Mac Kay.
Septiembre 2004.

Como impersonar (impersonate) un bloque de código

En algunas oportunidades es necesario ejecutar ciertas tareas o procesos con algún usuario que tenga otros permisos o privilegios distintos al usuario actual con que se está ejecutando el proceso principal o aplicación.


Como práctica realizada de forma habitual, pero que a su vez es muy riesgosa, se ejecutan las aplicaciones con permisos administrativos o de administrador, permitiendo que cualquier tipo de acceso a dispositivos o recursos, se hagan con todos los privilegios.


Una forma de sobrellevar este problema es utilizar impersonalización o suplantación (impersonate) en la ejecución de ciertos procesos como también de funciones o cierto código, es decir, que parte de toda mi aplicación se ejecute con otro usuario y otros permisos. No necesitamos ejecutar toda la aplicación con un permiso, sino que podemos ejecutar sólo algunas líneas con este nuevo usuario.


Veamos como se compone la función y su clase correspondiente para impersonar o suplantar un usuario. La clase no es necesaria crearla si van a implementar esta función dentro de alguna librería de clases de su aplicación.


using System;


using System.Runtime.InteropServices;


using System.Security.Principal;

 

public class Impersonalizacion


{


private const int LOGON32_PROVIDER_DEFAULT = 0;


            private const int LOGON32_LOGON_INTERACTIVE = 2;

 

            [DllImport(“advapi32.dll”, SetLastError=true)]


            public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

 

            [DllImport(“advapi32.dll”, EntryPoint=”DuplicateToken”, ExactSpelling=false, CharSet=CharSet.Auto, SetLastError=true)]


            public static extern int DuplicateToken(IntPtr ExistingTokenHandle, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

 

            public static WindowsImpersonationContext WinLogOn(string strUsuario, string strClave, string strDominio)


            {


                        IntPtr tokenDuplicate = new IntPtr(0);


                        IntPtr tokenHandle = new IntPtr(0);


                        if (LogonUser(strUsuario, strDominio, strClave, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))


                                   if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) != 0)


                                               return (new WindowsIdentity(tokenDuplicate)).Impersonate();


                        return null;


            }


}


Si hay cierto código que parece complicado, sólo deben enfocar su atención en una sola función, la llamada WinLogOn. Esta función recibe 3 parámetros, la cuenta de usuario, la clave y el dominio contra el que deberá ser validado el usuario.


Esta función retorna un objeto del tipo System.Security.Principal.WindowsImpersonationContext en caso de ser exitoso el login o null (nothing en VB.NET) en caso de no poder validar el usuario contra el dominio exigido.


Una vez que se tenga el objeto WindowsImpersonationContext, todas las líneas de código que se ejecuten posteriormente, lo harán con los permisos y privilegios de este nuevo usuario, hasta el llamado del método Undo() del objeto de tipo WindowsImpersonationContext. Cuando se llama a Undo(), la aplicación vuelve a correr con el usuario que estaba antes ejecutando la aplicación.


Veamos entonces código donde se muestre la usabilidad de la impersonalización de usuarios. Esta es una aplicación de consola.


class Class1


{


            [STAThread]


            static void Main(string[] args)


            {


                                  


                        WindowsImpersonationContext _objContext = null;

 

                        Console.WriteLine(QuienSoy());


                        _objContext = Impersonalizacion.WinLogOn(“patrick”, “clave”, “tango”);


                        if (_objContext != null)


                        {


                                   Console.WriteLine(QuienSoy());


                                   _objContext.Undo();


                        }


                        Console.WriteLine(QuienSoy());


                        Console.ReadLine();


            }

 

            internal static string QuienSoy()


            {


                        return WindowsIdentity.GetCurrent().Name;


            }


}


NOTA: Es importante garantizar que se produzca el Undo aunque haya alguna excepción de por medio dentro del código que se está impersonando. Si la línea marcada de rojo lanzase una excepción, el código que se ejecute después, que en este caso no son las líneas de más abajo sino que podría ser un catch o un finally de cualquier método que haya llamado a esta función, se hará con los privilegios del usuario específico, y no necesariamente con el usuario que estaba ejecutando la aplicación anteriormente. Esto sucederá por que no se ha realizado el logout o Undo.


Aplicando la corrección el código anterior, éste quedaría:


                        if (_objContext != null)


                        {


                                   try


                                   {


                                                           Console.WriteLine(QuienSoy());


                                   }


                                    finally


                                   {


                                                           _objContext.Undo();


                                   }


                        }


Patrick Mac Kay.
Septiembre 2004.

Como crear una coleccion de clases específicas

A veces hemos necesitado almacenar varios elementos de un tipo no estándar de .NET en alguna colección o arreglo. Hay alternativas que no son las mas ideales, pero que funcionan, como utilizar el objeto ArrayList o definir un arreglo de algún tamaño.Utilizar un ArrayList es muy fácil, pero lo que hace es crear un arreglo de objetos que debemos convertir al tipo necesario. Esta conversión la hace realizando una copia a otro arreglo. Este proceso es lento y consumidor de recursos.Utilizar un arreglo tiene sus ventajas y desventajas. Es evidente que manejar el tipo específico evita tener que hacer conversiones de tipos, pero también definir un arreglo de un tamaño específico nos puede hacer perder recursos (si es mas grande de lo que necesitamos) como también la necesidad de extenderlo si se queda chico.En este artículo veremos como construir una colección de clases específicas. La solución no es la óptima, pero es mejor que las dos anteriores. No es optima por que igual se utiliza “por debajo” una colección de objetos, pero la transformación es mas limpia y menos costosa en recursos. La solución óptima la tendremos en Whidbey, con la inclusión de Generics.

En los ejemplos se utilizará C#, pero la implementación de la colección se hará en tanto en C# como VB.


Veamos el siguiente ejemplo para una clase Documento, que contiene una colección de elementos de tipo Linea. En este caso, la clase específica que queremos convertir en colección es Linea.


Nuestra clase Linea esta definida de la siguiente forma. Tiene una propiedad de texto, con el valor.


public class Linea
{
   
private string _strValor = string.Empty;

    public Linea(string strValor)
    {
       
this._strValor = strValor;
    }
    public string Valor
    {
       
get { return this._strValor; }
    }
}

Para crear una colección de clases específicas, debe crear una nueva clase que herede de System.Collections.CollectionBase y crear los métodos para poder insertar, remover y acceder a los elementos de la colección. La clase base posee un método para remover (RemoveAt) pero que no hace validación de índices al ejecutarse. La clase de colección queda de la siguiente forma.


public class LineaCollection : System.Collections.CollectionBase
{

    public void Add(Linea objLinea)
    {
       
this.List.Add(objLinea);
    }
    public void Remove(int index)
    {
       
if (index <= Count – 1 || index >= 0)
        {
           
this.List.RemoveAt(index);
        }
    }
    public Linea this[int index]
    {
       
get { return ((Linea) (this.List[index])); }
       
set { this.List[index] = value; }
    }
}

¿Como queda nuestra clase Documento?. La propiedad Lineas es de sólo lectura ya que esta entrega la referencia al arreglo, y una vez con la referencia los cambios que se hagan en el arreglo persisten igual.


public class Documento
{
   
private string _strNombre = string.Empty;
   
private LineaCollection _objLineas = null;

    public Documento(string strNombre)
    {
       
this._strNombre = strNombre;
       
this._objLineas = new LineaCollection();
    }
    public LineaCollection Lineas
    {
       
get { return this._objLineas; }
    }
}

En un pequeño ejemplo de uso, creamos un documento, agregamos líneas y después las recorremos con un enumerador.

internal class Demo
{
    public Demo()
    {
        Documento _objDocumento = new Documento(“mi documento de prueba”);
        Linea _objLinea =
null;
        _objDocumento.Lineas.Add(new Linea(“linea 1”));
        _objLinea = new Linea(“Linea 2”);
        _objDocumento.Lineas.Add(_objLinea);
        foreach (Linea _objTemp in _objDocumento.Lineas)
        {
            System.Diagnostics.Debug.Write(_objTemp.Valor);
        }
    }
}

Por último, la misma colección, pero definida en VB.NET queda de la siguiente forma.

Public Class LineaCollection
    Inherits CollectionBase
    Public Sub Add(ByVal objLinea As Linea)
       
Me.List.Add(objLinea)
   
End Sub
    Public Sub Remove(ByVal index As Integer)
       
If index <= Count – 1 OrElse index >= 0 Then
            Me.List.RemoveAt(index)
       
End If
    End Sub
    Public Property Item(ByVal index As Integer) As Linea
       
Get
            Return CType(Me.List.Item(index), Linea)
       
End Get
        Set(ByVal Value As Linea)
           
Me.List.Item(index) = Value
       
End Set
    End Property

End Class