Equivalente a typedef em C #


326

Existe um equivalente typedef em C # ou alguma forma de obter algum tipo de comportamento semelhante? Eu pesquisei no Google, mas em todos os lugares que olho parece ser negativo. Atualmente, tenho uma situação semelhante à seguinte:

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

Agora, não é preciso um cientista de foguetes para descobrir que isso pode rapidamente levar a muitas digitações (desculpas pelo trocadilho horrível) ao tentar implementar um manipulador para esse evento. Acabaria sendo algo assim:

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();
}

Exceto, no meu caso, eu já estava usando um tipo complexo, não apenas um int. Seria bom se fosse possível simplificar um pouco ...

Editar: ie. talvez digitando o EventHandler em vez de precisar redefini-lo para obter um comportamento semelhante.

Respostas:


341

Não, não há equivalente verdadeiro de typedef. Você pode usar as diretivas 'using' em um arquivo, por exemplo

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

mas isso afetará apenas esse arquivo de origem. Em C e C ++, minha experiência é que typedefgeralmente é usado em arquivos .h que são amplamente incluídos - para que um único typedefpossa ser usado em todo o projeto. Essa capacidade não existe em C #, porque não há #includefuncionalidade em C # que permita incluir as usingdiretivas de um arquivo em outro.

Felizmente, o exemplo que você dá faz ter uma correção - conversão de grupo método implícito. Você pode alterar sua linha de inscrição do evento para apenas:

gcInt.MyEvent += gcInt_MyEvent;

:)


11
Eu sempre esqueço que você pode fazer isso. Talvez porque o Visual Studio sugira a versão mais detalhada. Mas eu estou bem com pressionando TAB duas vezes em vez de digitar o nome manipulador;)
— OregonGhost

11
Na minha experiência (que é escassa), você precisa especificar o nome completo do tipo, por exemplo: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; Está correto? Caso contrário, não parece considerar as usingdefinições acima.
— tunnuz

3
Não consegui converter typedef uint8 myuuid[16];através da diretiva "using". using myuuid = Byte[16];não compila. usingpode ser usado apenas para criar aliases de tipo . typedefparece ser muito mais flexível, pois pode criar um alias para uma declaração inteira (incluindo tamanhos de matriz). Existe alguma alternativa neste caso?
— Natelho

2
@natenho: Na verdade não. O mais próximo que você poderia chegar seria ter uma estrutura com um buffer de tamanho fixo, provavelmente.
— precisa saber é o seguinte

1
@tunnuz A menos que você especificá-lo dentro de um espaço de nomes
— John Smith

38

Jon realmente deu uma boa solução, eu não sabia que você poderia fazer isso!

Às vezes, recorri a herdar da classe e criar seus construtores. Por exemplo

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

Não é a melhor solução (a menos que sua montagem seja usada por outras pessoas), mas funciona.


41
Definitivamente, é um bom método, mas lembre-se de que esses tipos (irritantes) selados existem e não funcionarão lá. Eu realmente gostaria que o C # já apresentasse typedefs. É uma necessidade desesperada (especialmente para programadores em C ++).
— MasterMastic

1
Eu criei um projeto para essa situação chamado LikeType, que envolve o tipo subjacente em vez de herdá-lo. Ele também converterá implicitamente em TO para o tipo subjacente, para que você possa usar algo como public class FooList : LikeType<IReadOnlyList<Foo>> { ... }e depois usá-lo em qualquer lugar que você esperaria IReadOnlyList<Foo>. Minha resposta abaixo mostra mais detalhes.
— 22616 Matt

3
Também não inferirá o tipo Foose passado para, por exemplo, o método de modelo que aceita List<T>. Com o typedef adequado, seria possível.
— Aleksei Petrenko

18

Se você souber o que está fazendo, poderá definir uma classe com operadores implícitos para converter entre a classe de alias e a classe 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 };
    }
}

Na verdade, eu não aprovo isso e nunca usei algo assim, mas isso provavelmente poderia funcionar em algumas circunstâncias específicas.


Obrigado @palswim, cheguei aqui procurando por algo como "typedef string Identifier;" então sua sugestão pode ser exatamente o que eu preciso.
— Yoyo 31/05

6

O C # suporta alguma covariância herdada para delegados de eventos, portanto, um método como este:

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

Pode ser usado para se inscrever no seu evento, sem necessidade de transmissão explícita

gcInt.MyEvent += LowestCommonHander;

Você pode até usar a sintaxe lambda e o intellisense será feito para você:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};

Eu seriamente precisa ficar em torno de ter uma boa olhada em Linq ... para o registro, porém, eu estava construindo para 2,0 no momento (em VS 2008 embora)
— Matthew Scharley

Ah, também, eu posso me inscrever bem, mas para obter os argumentos do evento, preciso de uma conversão explícita e, de preferência, digite o código de verificação, apenas para estar do lado seguro.
— Matthew Scharley 02/10/08

9
A sintaxe está correta, mas eu não diria que é "sintaxe Linq"; ao contrário, é uma expressão lambda. Lambdas são um recurso de suporte que faz o Linq funcionar, mas são completamente independentes dele. Essencialmente, em qualquer lugar em que você possa usar um delegado, poderá usar uma expressão lambda.
— Scott Dorman

É justo, eu deveria ter dito lambda. Um delegado trabalharia no .Net 2, mas você precisaria declarar explicitamente o tipo genérico aninhado novamente.
— Keith

5

Eu acho que não há typedef. Você só pode definir um tipo de delegado específico em vez do genérico na GenericClass, ou seja,

public delegate GenericHandler EventHandler<EventData>

Isso tornaria mais curto. Mas e a seguinte sugestão:

Use o Visual Studio. Dessa forma, quando você digitou

gcInt.MyEvent += 

ele já fornece a assinatura completa do manipulador de eventos do Intellisense. Pressione TAB e está lá. Aceite o nome do manipulador gerado ou altere-o e pressione TAB novamente para gerar automaticamente o stub do manipulador.


2
Sim, foi o que fiz para gerar o exemplo. Mas voltar a olhar novamente DEPOIS do fato ainda pode ser confuso.
— Matthew Scharley

Eu sei o que você quer dizer. É por isso que eu gosto de manter minhas assinaturas de eventos curtas ou ficar longe da recomendação da FxCop de usar o Generic EventHandler <T> em vez do meu próprio tipo de delegado. Mas, em seguida, ficar com a versão de curto mão fornecido por Jon Skeet :)
— OregonGhost

2
Se você possui o ReSharper, ele diz que a versão longa é um exagero (pintando-a de cinza) e você pode usar uma "solução rápida" para se livrar dela novamente.
— Roger Lipscombe

5

C ++ e C # estão faltando maneiras fáceis de criar um novo tipo que é semanticamente idêntico a um tipo existente. Acho esses 'typedefs' totalmente essenciais para a programação com segurança de tipos e é uma pena que o c # não os tenha embutidos. A diferença entre void f(string connectionID, string username)to void f(ConID connectionID, UserName username)é óbvia ...

(Você pode obter algo semelhante em C ++ com aumento em BOOST_STRONG_TYPEDEF)

Pode ser tentador usar herança, mas isso tem algumas limitações principais:

  • não funcionará para tipos primitivos
  • o tipo derivado ainda pode ser convertido para o tipo original, ou seja, podemos enviá-lo para uma função que recebe nosso tipo original, isso derrota todo o propósito
  • não podemos derivar de classes seladas (e, por exemplo, muitas classes .NET são seladas)

A única maneira de conseguir algo semelhante em C # é compondo nosso tipo em uma nova classe:

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();
}

Embora isso funcione, é muito detalhado apenas para um typedef. Além disso, temos um problema de serialização (isto é, para Json), pois queremos serializar a classe através de sua propriedade Composed.

Abaixo está uma classe auxiliar que usa o "Padrão de Modelo Curiosamente Recorrente" para tornar isso muito mais simples:

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);
    }
  }

}

Com o Composer, a classe acima se torna simplesmente:

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

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

E além disso, o SomeTypeTypeDefserializará para o Json da mesma maneira que SomeTypefaz.

Espero que isto ajude !


4

Você pode usar uma biblioteca de código aberto e um pacote NuGet chamado LikeType que eu criei que fornecerão o GenericClass<int>comportamento que você está procurando.

O código ficaria assim:

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));
    }
}

o LikeType funcionaria para algo complexo como stackoverflow.com/questions/50404586/… ? Eu tentei brincar com ele e não consigo obter uma configuração de classe que funcione.
— Jay Croghan

Essa não é realmente a intenção da LikeTypebiblioteca. LikeTypeSeu principal objetivo é ajudar com a Obsessão primitiva e, como tal, não deseja que você possa passar pelo tipo de quebra automática como se fosse o tipo de quebra automática. Por exemplo, se eu fizer Age : LikeType<int>isso, se minha função precisar de um Age, quero garantir que meus chamadores estejam passando um Age, não um int.
— Matt Klein

Dito isto, acho que tenho uma resposta para sua pergunta, que postarei por lá.
— Matt Klein

3

Aqui está o código, aproveite !, peguei no dotNetReference o tipo "using" na linha de namespace 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"};
        }
    }
}

2

Para classes não seladas, simplesmente herde delas:

public class Vector : List<int> { }

Mas para classes seladas é possível simular o comportamento de typedef com essa classe 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
}

1

A melhor alternativa para a typedefque encontrei em C # é using. Por exemplo, eu posso controlar a precisão de flutuação por meio de sinalizadores de compilador com este código:

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

Infelizmente, é necessário que você coloque isso no topo de todos os arquivos em que você usa real_t. No momento, não há como declarar um tipo de espaço para nome global em C #.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.