Equivalente de typedef en C #

326

¿Hay un equivalente typedef en C #, o de alguna manera para obtener algún tipo de comportamiento similar? He buscado en Google, pero en todas partes me parece negativo. Actualmente tengo una situación similar a la siguiente:

class GenericClass<T> 
{
    public event EventHandler<EventData> MyEvent;
    public class EventData : EventArgs { /* snip */ }
    // ... snip
}

Ahora, no hace falta ser un experto en cohetes para darse cuenta de que esto puede conducir rápidamente a una gran cantidad de tipeo (disculpas por el horrible juego de palabras) al intentar implementar un controlador para ese evento. Terminaría siendo algo como esto:

GenericClass<int> gcInt = new GenericClass<int>;
gcInt.MyEvent += new EventHandler<GenericClass<int>.EventData>(gcInt_MyEvent);
// ...

private void gcInt_MyEvent(object sender, GenericClass<int>.EventData e)
{
    throw new NotImplementedException();
}

Excepto que, en mi caso, ya estaba usando un tipo complejo, no solo un int. Sería bueno si fuera posible simplificar esto un poco ...

Editar: es decir. quizás escriba el EventHandler en lugar de tener que redefinirlo para obtener un comportamiento similar.

Matthew Scharley
fuente

Respuestas:

341

No, no hay un verdadero equivalente de typedef. Puede usar las directivas 'using' dentro de un archivo, por ej.

using CustomerList = System.Collections.Generic.List<Customer>;

pero eso solo afectará ese archivo fuente. En C y C ++, mi experiencia es que typedefgeneralmente se usa dentro de archivos .h que se incluyen ampliamente, por lo que typedefse puede usar uno solo en todo un proyecto. Esa capacidad no existe en C #, porque no hay #includefuncionalidad en C # que le permita incluir las usingdirectivas de un archivo en otro.

Afortunadamente, el ejemplo que das lo hace tener una solución - la conversión del grupo método implícito. Puede cambiar la línea de suscripción de su evento a solo:

gcInt.MyEvent += gcInt_MyEvent;

:)

Jon Skeet
fuente
11
Siempre olvido que puedes hacer esto. Tal vez porque Visual Studio sugiere la versión más detallada. Pero estoy bien presionando TAB dos veces en lugar de escribir el nombre del controlador;)
OregonGhost
11
En mi experiencia (que es escasa), debe especificar el nombre de tipo completo, por ejemplo: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; ¿Es correcto? De lo contrario, no parece considerar las usingdefiniciones anteriores.
tunnuz
3
No pude convertir a typedef uint8 myuuid[16];través de la directiva "uso". using myuuid = Byte[16];no compila usingse puede usar solo para crear alias de tipo . typedefparece ser mucho más flexible, ya que puede crear un alias para una declaración completa (incluidos los tamaños de matriz). ¿Hay alguna alternativa en este caso?
natenho
2
@natenho: En realidad no. Probablemente, lo más cerca que podría estar sería tener una estructura con un búfer de tamaño fijo.
Jon Skeet
1
@tunnuz A menos que lo especifiques dentro de un espacio de nombres
John Smith
38

Jon realmente dio una buena solución, ¡no sabía que podías hacer eso!

A veces recurrí a heredar de la clase y crear sus constructores. P.ej

public class FooList : List<Foo> { ... }

No es la mejor solución (a menos que su ensamblaje sea utilizado por otras personas), pero funciona.

Jonathan C Dickinson
fuente
41
Definitivamente es un buen método, pero tenga en cuenta que existen esos tipos sellados (molestos), y no funcionará allí. Realmente desearía que C # ya introdujera typedefs. Es una necesidad desesperada (especialmente para programadores de C ++).
MasterMastic
1
He creado un proyecto para esta situación llamado LikeType que envuelve el tipo subyacente en lugar de heredarlo. También convertirá implícitamente TO al tipo subyacente, por lo que podría usar algo como public class FooList : LikeType<IReadOnlyList<Foo>> { ... }y luego usarlo en cualquier lugar donde espere a IReadOnlyList<Foo>. Mi respuesta a continuación muestra más detalles.
Matt Klein
3
Tampoco inferirá el tipo Foosi se pasa, por ejemplo, al método de plantilla que acepta List<T>. Con el typedef apropiado sería posible.
Aleksei Petrenko
18

Si sabe lo que está haciendo, puede definir una clase con operadores implícitos para convertir entre la clase de alias y la clase real.

class TypedefString // Example with a string "typedef"
{
    private string Value = "";
    public static implicit operator string(TypedefString ts)
    {
        return ((ts == null) ? null : ts.Value);
    }
    public static implicit operator TypedefString(string val)
    {
        return new TypedefString { Value = val };
    }
}

En realidad no estoy de acuerdo con esto y nunca he usado algo como esto, pero esto probablemente podría funcionar para algunas circunstancias específicas.

palswim
fuente
Gracias @palswim, llegué aquí buscando algo como "typedef string Identifier;" entonces su sugerencia puede ser justo lo que necesito.
yoyo
6

C # admite algunas covarianzas heredadas para delegados de eventos, por lo que un método como este:

void LowestCommonHander( object sender, EventArgs e ) { ... } 

Se puede usar para suscribirse a su evento, no se requiere un elenco explícito

gcInt.MyEvent += LowestCommonHander;

Incluso puede usar la sintaxis lambda y todo el intellisense se hará por usted:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
Keith
fuente
Realmente necesito lograr echar un buen vistazo a Linq ... para el registro, sin embargo, estaba construyendo para 2.0 en ese momento (aunque en VS 2008)
Matthew Scharley
Ah, también, puedo suscribirme bien, pero para llegar a los argumentos del evento necesito un reparto explícito, y preferiblemente escribir código de verificación, solo para estar seguro.
Matthew Scharley
99
La sintaxis es correcta, pero no diría que es "sintaxis de Linq"; más bien es una expresión lambda. Las lambdas son una característica de soporte que hace que Linq funcione, pero son completamente independientes de ella. Esencialmente, en cualquier lugar donde pueda usar un delegado, puede usar una expresión lambda.
Scott Dorman
Punto justo, debería haber dicho lambda. Un delegado funcionaría en .Net 2, pero necesitaría declarar explícitamente el tipo genérico anidado nuevamente.
Keith
5

Creo que no hay typedef. Solo puede definir un tipo de delegado específico en lugar del genérico en GenericClass, es decir

public delegate GenericHandler EventHandler<EventData>

Esto lo haría más corto. Pero qué pasa con la siguiente sugerencia:

Usa Visual Studio. De esta manera, cuando escribiste

gcInt.MyEvent += 

ya proporciona la firma completa del controlador de eventos de Intellisense. Presione TAB y está ahí. Acepte el nombre del controlador generado o cámbielo, y luego presione TAB nuevamente para generar automáticamente el código auxiliar del controlador.

OregonGhost
fuente
2
Sí, eso es lo que hice para generar el ejemplo. Pero volver a verlo nuevamente DESPUÉS del hecho puede ser confuso.
Matthew Scharley
Yo sé lo que quieres decir. Es por eso que me gusta mantener cortas las firmas de mis eventos o alejarme de la recomendación de FxCop de usar Generic EventHandler <T> en lugar de mi propio tipo de delegado. Pero luego, quédese con la versión abreviada proporcionada por Jon Skeet :)
OregonGhost
2
Si tienes ReSharper, te dirá que la versión larga es exagerada (coloreándola en gris), y puedes usar una "solución rápida" para deshacerte de ella nuevamente.
Roger Lipscombe
5

Tanto C ++ como C # carecen de formas fáciles de crear un nuevo tipo que sea semánticamente idéntico a un tipo existente. Encuentro que tales 'typedefs' son totalmente esenciales para la programación segura de tipos y es una verdadera lástima que C # no los tenga incorporados. La diferencia entre void f(string connectionID, string username)a void f(ConID connectionID, UserName username)es obvia ...

(Puede lograr algo similar en C ++ con impulso en BOOST_STRONG_TYPEDEF)

Puede ser tentador usar la herencia, pero eso tiene algunas limitaciones importantes:

  • no funcionará para tipos primitivos
  • el tipo derivado todavía se puede convertir al tipo original, es decir, podemos enviarlo a una función que recibe nuestro tipo original, esto anula todo el propósito
  • no podemos derivar de clases selladas (y es decir, muchas clases .NET están selladas)

La única forma de lograr algo similar en C # es componiendo nuestro tipo en una nueva clase:

Class SomeType { 
  public void Method() { .. }
}

sealed Class SomeTypeTypeDef {
  public SomeTypeTypeDef(SomeType composed) { this.Composed = composed; }

  private SomeType Composed { get; }

  public override string ToString() => Composed.ToString();
  public override int GetHashCode() => HashCode.Combine(Composed);
  public override bool Equals(object obj) => obj is TDerived o && Composed.Equals(o.Composed); 
  public bool Equals(SomeTypeTypeDefo) => object.Equals(this, o);

  // proxy the methods we want
  public void Method() => Composed.Method();
}

Si bien esto funcionará, es muy detallado solo para un typedef. Además, tenemos un problema con la serialización (es decir, a Json) ya que queremos serializar la clase a través de su propiedad Composed.

A continuación se muestra una clase auxiliar que utiliza el "Patrón de plantilla curiosamente recurrente" para simplificar esto:

namespace Typedef {

  [JsonConverter(typeof(JsonCompositionConverter))]
  public abstract class Composer<TDerived, T> : IEquatable<TDerived> where TDerived : Composer<TDerived, T> {
    protected Composer(T composed) { this.Composed = composed; }
    protected Composer(TDerived d) { this.Composed = d.Composed; }

    protected T Composed { get; }

    public override string ToString() => Composed.ToString();
    public override int GetHashCode() => HashCode.Combine(Composed);
    public override bool Equals(object obj) => obj is Composer<TDerived, T> o && Composed.Equals(o.Composed); 
    public bool Equals(TDerived o) => object.Equals(this, o);
  }

  class JsonCompositionConverter : JsonConverter {
    static FieldInfo GetCompositorField(Type t) {
      var fields = t.BaseType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
      if (fields.Length!=1) throw new JsonSerializationException();
      return fields[0];
    }

    public override bool CanConvert(Type t) {
      var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
      return fields.Length == 1;
    }

    // assumes Compositor<T> has either a constructor accepting T or an empty constructor
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
      while (reader.TokenType == JsonToken.Comment && reader.Read()) { };
      if (reader.TokenType == JsonToken.Null) return null; 
      var compositorField = GetCompositorField(objectType);
      var compositorType = compositorField.FieldType;
      var compositorValue = serializer.Deserialize(reader, compositorType);
      var ctorT = objectType.GetConstructor(new Type[] { compositorType });
      if (!(ctorT is null)) return Activator.CreateInstance(objectType, compositorValue);
      var ctorEmpty = objectType.GetConstructor(new Type[] { });
      if (ctorEmpty is null) throw new JsonSerializationException();
      var res = Activator.CreateInstance(objectType);
      compositorField.SetValue(res, compositorValue);
      return res;
    }

    public override void WriteJson(JsonWriter writer, object o, JsonSerializer serializer) {
      var compositorField = GetCompositorField(o.GetType());
      var value = compositorField.GetValue(o);
      serializer.Serialize(writer, value);
    }
  }

}

Con Composer, la clase anterior se vuelve simplemente:

sealed Class SomeTypeTypeDef : Composer<SomeTypeTypeDef, SomeType> {
   public SomeTypeTypeDef(SomeType composed) : base(composed) {}

   // proxy the methods we want
   public void Method() => Composed.Method();
}

Y además SomeTypeTypeDef, serializará a Json de la misma manera que lo SomeTypehace.

Espero que esto ayude !

kofifus
fuente
4

Puede usar una biblioteca de código abierto y un paquete NuGet llamado LikeType que creé que le dará el GenericClass<int>comportamiento que está buscando.

El código se vería así:

public class SomeInt : LikeType<int>
{
    public SomeInt(int value) : base(value) { }
}

[TestClass]
public class HashSetExample
{
    [TestMethod]
    public void Contains_WhenInstanceAdded_ReturnsTrueWhenTestedWithDifferentInstanceHavingSameValue()
    {
        var myInt = new SomeInt(42);
        var myIntCopy = new SomeInt(42);
        var otherInt = new SomeInt(4111);

        Assert.IsTrue(myInt == myIntCopy);
        Assert.IsFalse(myInt.Equals(otherInt));

        var mySet = new HashSet<SomeInt>();
        mySet.Add(myInt);

        Assert.IsTrue(mySet.Contains(myIntCopy));
    }
}
Matt Klein
fuente
¿LikeType funcionaría para algo complejo como stackoverflow.com/questions/50404586/… ? He intentado jugar con él y no puedo obtener una configuración de clase que funcione.
Jay Croghan
Esa no es realmente la intención de la LikeTypebiblioteca. LikeTypeEl propósito principal es ayudar con la obsesión primitiva , y como tal, no quiere que puedas pasar el tipo envuelto como si fuera el tipo de envoltorio. Como en, si hago Age : LikeType<int>entonces si mi función necesita un Age, quiero asegurarme de que mis llamadas pasen un Age, no un int.
Matt Klein
Dicho esto, creo que tengo una respuesta a su pregunta, que publicaré allí.
Matt Klein
3

Aquí está el código para ello, ¡disfrútelo! Lo tomé de dotNetReference, escriba la instrucción "using" dentro de la línea de espacio de nombres 106 http://referencesource.microsoft.com/#mscorlib/microsoft/win32/win32native.cs

using System;
using System.Collections.Generic;
namespace UsingStatement
{
    using Typedeffed = System.Int32;
    using TypeDeffed2 = List<string>;
    class Program
    {
        static void Main(string[] args)
        {
        Typedeffed numericVal = 5;
        Console.WriteLine(numericVal++);

        TypeDeffed2 things = new TypeDeffed2 { "whatever"};
        }
    }
}
shakram02
fuente
2

Para las clases no selladas, simplemente herede de ellas:

public class Vector : List<int> { }

Pero para las clases selladas es posible simular el comportamiento typedef con dicha clase base:

public abstract class Typedef<T, TDerived> where TDerived : Typedef<T, TDerived>, new()
{
    private T _value;

    public static implicit operator T(Typedef<T, TDerived> t)
    {
        return t == null ? default : t._value;
    }

    public static implicit operator Typedef<T, TDerived>(T t)
    {
        return t == null ? default : new TDerived { _value = t };
    }
}

// Usage examples

class CountryCode : Typedef<string, CountryCode> { }
class CurrencyCode : Typedef<string, CurrencyCode> { }
class Quantity : Typedef<int, Quantity> { }

void Main()
{
    var canadaCode = (CountryCode)"CA";
    var canadaCurrency = (CurrencyCode)"CAD";
    CountryCode cc = canadaCurrency;        // Compilation error
    Concole.WriteLine(canadaCode == "CA");  // true
    Concole.WriteLine(canadaCurrency);      // CAD

    var qty = (Quantity)123;
    Concole.WriteLine(qty);                 // 123
}
Vlad Rudenko
fuente
1

La mejor alternativa a la typedefque he encontrado en C # es using. Por ejemplo, puedo controlar la precisión de flotación a través de los indicadores del compilador con este código:

#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif

Desafortunadamente, requiere que coloque esto en la parte superior de cada archivo donde lo use real_t. Actualmente no hay forma de declarar un tipo de espacio de nombres global en C #.

Aaron Franke
fuente