c# que Guid fuertemente tipado como estructura genérica




que es tipado dinamico en python (3)

Ya hago dos veces el mismo error en el código como el siguiente:

void Foo(Guid appId, Guid accountId, Guid paymentId, Guid whateverId)
{
...
}

Guid appId = ....;
Guid accountId = ...;
Guid paymentId = ...;
Guid whateverId =....;

//BUG - parameters are swapped - but compiler compiles it
Foo(appId, paymentId, accountId, whateverId);

OK, quiero evitar estos errores, así que creé GUIDs fuertemente tipados:

[ImmutableObject(true)]
public struct AppId
{
    private readonly Guid _value;

    public AppId(string value)
    {            
        var val = Guid.Parse(value);
        CheckValue(val);
        _value = val;
    }      

    public AppId(Guid value)
    {
        CheckValue(value);
        _value = value;           
    }

    private static void CheckValue(Guid value)
    {
        if(value == Guid.Empty)
            throw new ArgumentException("Guid value cannot be empty", nameof(value));
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

Y otro para PaymentId:

[ImmutableObject(true)]
public struct PaymentId
{
    private readonly Guid _value;

    public PaymentId(string value)
    {            
        var val = Guid.Parse(value);
        CheckValue(val);
        _value = val;
    }      

    public PaymentId(Guid value)
    {
        CheckValue(value);
        _value = value;           
    }

    private static void CheckValue(Guid value)
    {
        if(value == Guid.Empty)
            throw new ArgumentException("Guid value cannot be empty", nameof(value));
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

Estas estructuras son casi iguales, hay mucha duplicación de código. No es?

No puedo encontrar una manera elegante de resolverlo, excepto el uso de class en lugar de struct. Preferiría usar struct, debido a las comprobaciones nulas, menos espacio en la memoria, sin sobrecarga del recolector de basura, etc.

¿Tienes alguna idea de cómo usar struct sin duplicar código?


Hacemos lo mismo, funciona muy bien.

Sí, es mucho copiar y pegar, pero eso es exactamente para lo que sirve la generación de código.

En Visual Studio, puedes usar plantillas T4 para esto. Básicamente, escribe tu clase una vez y luego tienes una plantilla donde dices "Quiero esta clase para Aplicación, Pago, Cuenta, ..." y Visual Studio te generará un archivo de código fuente para cada uno.

De esa manera, tiene una única fuente (la plantilla T4) donde puede realizar cambios si encuentra un error en sus clases y se propagará a todos sus identificadores sin que tenga que pensar en cambiarlos todos.


Es posible que pueda usar subclases con un lenguaje de programación diferente.


En primer lugar, esta es una muy buena idea. Un breve aparte:

Me gustaría que C # hiciera más fácil crear envoltorios de tipo barato alrededor de enteros, cadenas, identificadores, etc. Somos muy "felices de cadena" y "enteros felices" como programadores; muchas cosas se representan como cadenas y enteros que podrían tener más información rastreada en el sistema de tipos; No queremos asignar nombres de clientes a direcciones de clientes. Hace un tiempo escribí una serie de publicaciones en el blog (¡nunca terminé!) Sobre la escritura de una máquina virtual en OCaml, y una de las mejores cosas que hice fue envolver cada entero en la máquina virtual con un tipo que indica su propósito. ¡Eso evitó tantos bichos! OCaml hace que sea muy fácil crear pequeños tipos de envoltorios; C # no lo hace.

En segundo lugar, no me preocuparía demasiado por duplicar el código. En su mayoría es fácil copiar y pegar, y es poco probable que edite el código mucho o cometa errores. Pasa tu tiempo resolviendo problemas reales. Un pequeño código copiado no es un gran problema.

Si desea evitar el código copiado y copiado, le sugeriría usar genéricos como este:

struct App {}
struct Payment {}

[ImmutableObject(true)]
public struct Id<T>
{
    private readonly Guid _value;
    public Id(string value)
    {            
        var val = Guid.Parse(value);
        CheckValue(val);
        _value = val;
    }

    public Id(Guid value)
    {
        CheckValue(value);
        _value = value;           
    }

    private static void CheckValue(Guid value)
    {
        if(value == Guid.Empty)
            throw new ArgumentException("Guid value cannot be empty", nameof(value));
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

Y ahora que has terminado. Tiene los tipos Id<App> e Id<Payment> lugar de AppId y PaymentId , pero aún no puede asignar una Id<App> a Id<Payment> o Guid .

Además, si le gusta usar AppId y PaymentId , en la parte superior de su archivo puede decir

using AppId = MyNamespace.Whatever.Id<MyNamespace.Whatever.App>

y así.

En tercer lugar, probablemente necesitará algunas características más en su tipo; Supongo que esto no está hecho todavía. Por ejemplo, probablemente necesitará igualdad, de modo que pueda verificar si dos ID son iguales.

En cuarto lugar, tenga en cuenta que el default(Id<App>) aún le proporciona un identificador de "guid vacío", por lo que su intento de evitar eso no funciona; Todavía será posible crear uno. Realmente no hay una buena manera de evitar eso.





guid