Etiqueta: VB.NET

Temas relacionados con Visual Basic para .NET Framework

  • Crear un objeto a partir de una cadena con Activator.CreateInstance

     

    Pues eso… como el título indica, te voy a explicar cómo crear (instanciar sería más correcto) un objeto a partir del "nombre" de una clase, pero estando ese nombre dentro de una cadena. Y como de costumbre, con el código de ejemplo tanto para (mi querido) Visual Basic como para (mi obligado) C# 😉

    Lo de "obligado" también lo puedes tomar por "obrigado" que en portugués significa "gracias", de agradecimiento, no te vayas a pensar que el C# me hace gracia… bueno, algunas veces me hago a la idea que me está guiñando… (por lo del punto y coma) 😉

     

    Vamos al tema.

    Si tienes una cadena con el nombre de una clase, puedes crear una nueva instancia de esa clase utilizando la clase Activator, más concretamente el método compartido CreateInstance.

    Para que funcione la conversión de cadena a objeto del tipo correcto debes tener en cuenta que:

    1. Indiques el espacio de nombres del tipo en la cadena
    2. La clase puede ser una clase derivada (o implementada)
    3. El tipo tenga un constructor público y sin parámetros
    4. Las clases abstractas (abstract / MustInherit) no sirven ya que no se pueden instanciar
    5. Que la clase a crear esté accesible, vamos que si tú no puedes crear el objeto ni indicando el tipo de forma explícita el Activator no creo que haga milagros…

     

    Una vez que sabemos las condiciones que debemos seguir para instanciar clases a partir del contenido de un string vamos a ponerlo a prueba con unos ejemplos en los que comprobaremos lo dicho en la lista anterior y algo más.

    El código para hacer la conversión lo podemos simplificar al máximo, pero en ese caso sólo deberíamos usarlo para crear clases que cumplan las 2 primeras condiciones indicadas en la lista anterior.
    Para casos indicados en los puntos 3 y 4 habría que usar un bloque try/catch para detectar la excepción que ocurrirá, ya que el tipo es correcto pero no se puede instanciar.
    Para los casos que cumplan la última condición (el tipo no existe o no está accesible) podemos usar una comprobación que en realidad es para saber si el tipo es del mismo tipo a convertir o se deriva de él, esa comprobación la haremos por medio del método IsAssignableFrom (definido en la clase Type).

    Veamos primero el código que hace la conversión sin más comprobaciones 
    (el código completo lo he puesto en un método para que sea más fácil hacer varias de las pruebas).

    VB:

    Dim laClase = "ActivatorVB.Colega"
    
    Dim elTipo As Type = Type.GetType(laClase)
    
    Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)
    
    If obj IsNot Nothing Then
        obj.Nombre = "Pepe"
        obj.Email = "pepe@outlook.com"
    End If
    
    

     

    C#:

    var laClase = "ActivatorCS.Colega";
    
    Type elTipo = Type.GetType(laClase);
    
    Colega obj = Activator.CreateInstance(elTipo) as Colega;
    
    if (obj != null)
    {
        obj.Nombre = "Pepe";
        obj.Email = "pepe@outlook.com";
    }
    

     

    Un par de aclaraciones (tanto para VB como para C#, las aclaraciones) sobre este código:

    En VB utilizo TryCast y en C# utilizo as para hacer la conversión ya que de esa forma, si el objeto no se puede crear se devuelve un valor nulo.

    Si en VB utilizara CType o DirectCast o en C# hiciera la conversión con (Colega) y la conversión falla, produciría un error.

    Aunque en realidad esto sólo nos salvará de los casos indicados en el punto 5 de las "condiciones" para realizar la instanciación de la clase a partir de una cadena. Pero bueno, ya sabes que hay varias formas de hacer conversiones entre tipos 😉

     

    Para hacer la comprobación de si el tipo es adecuado por medio del método IsAssignableFrom lo haríamos de la siguiente forma:

     

    '  VB
    ' Esto controlará las clases que no se basan en Colega
    If Not GetType(Colega).IsAssignableFrom(elTipo) Then Console.WriteLine("El tipo: {0} no es del tipo adecuado." & vbLf & "", laClase) Return End If // C# // Esto controlará las clases que no se basan en Colega if (!typeof(Colega).IsAssignableFrom(elTipo)) { Console.WriteLine("El tipo: {0} no es del tipo adecuado.\n", laClase); return; }

     

    Ya solo me queda ponerte todo el código de prueba en el que he hecho las pruebas esas que conforman la lista para estar seguro de que es así…

    Te recomiendo que hagas tú las tuyas propias, incluso usando las partes comentadas en dicho código.

    En el método Main2 de dicho código he puesto un ejemplo de una clase que implementa una interfaz.

     

    Nota:

    El código completo lo pongo aparte para que no llene tanto esto.

    Además te pongo separado el de Visual Basic de el de C# y tu eliges el que quieras.

    También está publicado en Pastebin.

     

    Espero que te sea de utilidad.

    Nos vemos.

    Guillermo

    P.S.

    Los enlaces:

    El de VB en Pastebin

    El de C# en Pastebin

    El de VB en mi blog

    El de C# en mi blog

    …

    elGuille_luna300x200

  • Código de cómo instanciar una clase a partir del contenido de una cadena (Visual Basic)

     

    Lo dicho el código completo para Visual Basic del ejemplo comentado en:

    Crear un objeto a partir de una cadena con Activator.CreateInstance

     

    (el enlace al de C# está en ese mismo artículo)

     

    Namespace ActivatorVB
    
        Public NotInheritable Class CrearClase
    
            Public Shared Sub Main()
    
                'CrearClase2.Prueba()
    
                'Main2()
    
                Prueba()
    
                Console.ReadKey()
    
            End Sub
    
            Private Shared Sub Prueba()
                ' La clase a crear
                Dim elNombre = "ActivatorVB.Colega"
    
                crearColega(elNombre)
    
                ' Esta clase se llama igual,
                ' pero no es del mismo tipo
                ' y no pasaría la prueba de IsAssignableFrom,
                ' aunque no daría error al hacer la conversión
                ' pero no crearía una instancia,
                ' por tanto Activator devolvería nulo,
                ' en realidad el valor nulo lo devuelve
                ' la operación de conversión de tipos
                ' por eso en VB utilizo TryCast en lugar
                ' de DirectCast o CType
                elNombre = "OtroEspacio.Colega"
                crearColega(elNombre)
    
                ' esta clase se basa en la que queremos crear
                ' aunque esté en otro espacio de nombres
                elNombre = "OtroEspacio.Coleguilla"
                crearColega(elNombre)
    
                '' Esta no tiene constructor
                'elNombre = "OtroEspacio.Coleguilla2"
                'crearColega(elNombre)
    
    
                ' este tipo no existe,
                ' pero debemos usar IsAssignableFrom
                ' para no provocar una excepción
                elNombre = "Activator.Colega"
                crearColega(elNombre)
    
                ' esta se basa en la nuestra
                ' pero es abstracta (no tiene constructor)
                ' ya dará un error al crearla
                ' aunque pasaría la prueba de IsAssignableFrom
                Try
                    elNombre = "ActivatorVB.ColegaAbstracto"
                    crearColega(elNombre)
    
                Catch ex As Exception
                    Console.WriteLine("Mrw! (o Ups!) esta clase {0} da error: {1}",
                                      elNombre, ex.Message)
    
                End Try
    
            End Sub
    
            Private Shared Sub crearColega(laClase As String)
                Dim elTipo As Type = Type.GetType(laClase)
    
                If Not GetType(Colega).IsAssignableFrom(elTipo) Then
                    Console.WriteLine("El tipo: {0} no es del tipo adecuado." & vbLf & "",
                                      laClase)
                    Return
    
                End If
    
                'Dim obj1 As Object = Activator.CreateInstance(elTipo)
                'Dim obj2 As Colega = CType(Activator.CreateInstance(elTipo), Colega)
                'Dim obj3 As Colega = DirectCast(Activator.CreateInstance(elTipo), Colega)
    
                Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)
    
                If obj IsNot Nothing Then
                    obj.Nombre = "Pepe"
                    obj.Email = "pepe@outlook.com"
    
                    Console.WriteLine("Objeto creado correctamente: {0}{1}{2}, {3}",
                                      obj.GetType().Name, vbCrLf,
                                      obj.Nombre, obj.Email)
                Else
                    Console.WriteLine("Parece que el tipo: {0} no es del tipo adecuado.",
                                      laClase)
                End If
    
                Console.WriteLine()
            End Sub
    
            Sub pruebaDirecta()
                Dim laClase = "ActivatorVB.Colega"
    
                Dim elTipo As Type = Type.GetType(laClase)
    
                Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)
    
                If obj IsNot Nothing Then
                    obj.Nombre = "Pepe"
                    obj.Email = "pepe@outlook.com"
                End If
    
            End Sub
    
            Shared Sub Main2()
                Dim laClase = "ActivatorVB.unColega"
    
                Dim elTipo As Type = Type.GetType(laClase)
    
                Dim obj As IColega = TryCast(Activator.CreateInstance(elTipo), IColega)
    
                If obj IsNot Nothing Then
                    obj.Nombre = "Pepe"
                End If
    
            End Sub
    
        End Class
    
        Public Interface IColega
            Property Nombre As String
        End Interface
    
        Public Class unColega
            Implements IColega
    
            Public Property Nombre As String Implements IColega.Nombre
        End Class
    
        Public Class Colega
            Public Property Nombre As String
            Public Property Email As String
        End Class
    
        ' Clase no instanciable
        Public MustInherit Class ColegaAbstracto
            Inherits Colega
    
            Public Property Edad As Integer
        End Class
    
    End Namespace
    
    Namespace OtroEspacio
    
        Public Class Colega
            Public Property Nombre As String
            Public Property Edad As Integer
        End Class
    
        Public Class Coleguilla
            Inherits ActivatorVB.Colega
    
            Public Property Edad As Integer
    
        End Class
    
        ' las clase selladas (sealed / NotInheritable)
        ' se pueden usar, pero siempre deben tener 
        ' un constructor público sin parámetros
        Public NotInheritable Class Coleguilla2
            Inherits ActivatorVB.Colega
    
            'Protected Sub New()
            'End Sub
    
            'Private Sub New()
            'End Sub
    
            Public Property Edad As Integer
        End Class
    
    End Namespace
    
    
    

     

    Nos vemos.

    Guillermo

    P.S. (06/Ene/13)

    He actualizado el código que esta entrada la edité desde el editor de WordPress y se hizo un follón, quitando los espacios y el formato… GRRRR!

    …

  • hay que ver… como es el Format con las llaves…

     

    Pues eso… que si usas Format, en cualquiera de sus encarnaciones: String.Format, StringBuilder.AppendFormat, Console.WriteLine, (incluso la función Format de Visual Basic) etc. y estas usando llaves para indicar parámetros (marcadores de posición que dicen en la documentación), mucho cuidado con que la cadena que estás formateando no tenga también llaves, porque entonces la hemos "pifiao".

     

    El problema

     

    Por ejemplo, si tienes esta cadena (o este código XAML):

     

    Dim xaml = <StackPanel Style="{StaticResource spImagen}">
                   <Image Source="ms-appx:///contenido/img/{0}"
                       Style="{StaticResource Imagen}"/>
               </StackPanel>
    
    

     

    Y pretendes usarlo con algo como esto:

    VB:

     

    sb.AppendFormat(xaml.ToString & "{1}",
                    sText.Replace(".tif", ".jpg"), vbCrLf)
    

     

    C#:

    Nota:

    En C# no se puede usar el contenido XML directamente, pero no es complicado adaptarlo, eso sí, es algo más laborioso que en Visual Basic, todo hay que decirlo. Pero para este caso que es poco código lo pasaremos por alto 😉

     

    string xaml = @"<StackPanel Style=""{StaticResource spImagen}"">
                        <Image Source=""ms-appx:///contenido/img/{0}""
                            Style=""{StaticResource Imagen}""/>
                    </StackPanel>";
    
    sb.AppendFormat(xaml + "\n",
                    sText.Replace(".tif", ".jpg"));
    

     

    Si usamos este código (ya sea con VB o con C#) el compilador producirá una excepción de formato no válido (FormatException) y es porque el "formateador" se encuentra con llaves (de inicio y/o de cierre) que no tienen un valor numérico asignado.

     

    FormatException

    Figura 1. El error en la aplicación de C# (es el mismo que en VB)

     

     

    La solución

     

    Pues… quitar las cadenas con las llaves que no sean "marcadores" y agregarlas en el propio AppendFormat:

     

    VB:

     

    Dim xaml = <Paragraph TextAlignment="Center">
                   <InlineUIContainer>
                       <StackPanel Style="{0}">
                           <Image Source="ms-appx:///contenido/img/{1}"
                               Style="{2}"/>
                       </StackPanel>
                   </InlineUIContainer>
               </Paragraph>
    
    sb.AppendFormat(xaml.ToString & "{3}",
                    "{StaticResource spImagen}",
                    sText.Replace(".tif", ".jpg"),
                    "{StaticResource Imagen}", vbCrLf)
    

     

    C#:

     

    string xaml = @"<StackPanel Style=""{0}"">
                        <Image Source=""ms-appx:///contenido/img/{1}""
                            Style=""{2}""/>
                    </StackPanel>";
    
    sb.AppendFormat(xaml + "\n","{StaticResource spImagen}",
                    sText.Replace(".tif", ".jpg"), "{StaticResource Imagen}");
    

     

    Es decir, ponemos para cada una de las "llaves fatídicas" una cadena con un marcador (de posición) y ponemos esas cadena (con sus llaves) en los parámetros de la función Format y asunto solucionado.

     

    Nota del Guille:

    Mira que esto mismo ya me pasó ayer (o hace un puñao de horas) y después me volvió a pasar, pero he estado un buen rato dándole vueltas al tema (se me olvidó que volví a cambiar el código XAML) y no daba con el dichoso error… eso me pasa por dos razones:

    1.- no tener buena memoria, no dormir lo suficiente, ser más cabezón que el Visual Studio

    2.- no usar suficientes Try/catch para "acorralar" el error…

    En fin… lo importante es que he dado con el error y que te lo cuento por si te pasa a ti.

     

    Si todo va bien, ya mismo te explicaré para qué estoy usando ese código (el de Visual Basic, ya que el de C# sólo lo he escrito para aquellos que les gustan los puntos y comas)…

     

    Lo dicho, espero que te pueda ser de ayuda… esa siempre es la intención 😉

     

    Nos vemos.

    Guillermo

    …