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.