C #의 typedef와 동일


326

C #에 동등한 typedef가 있습니까, 아니면 어쨌든 비슷한 행동을 취하기 위해 어떻습니까? 인터넷 검색을 해봤지만 어디에서나 부정적인 것으로 보입니다. 현재 다음과 유사한 상황이 있습니다.

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

이제 로켓 과학자가 해당 이벤트에 대한 핸들러를 구현하려고 할 때 많은 타이핑 (끔찍한 말장난에 대한 사과)을 초래할 수 있음을 알아내는 데 로켓 과학자가 필요하지 않습니다. 결국 다음과 같이됩니다.

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

내 경우에는 이미 int뿐만 아니라 복잡한 유형을 사용하고 있습니다. 이것을 조금 단순화하는 것이 가능하다면 좋을 것입니다 ...

편집 : 즉. 비슷한 동작을 얻기 위해 재정의 해야하는 대신 EventHandler를 typedefing 할 수 있습니다.

답변:


341

아니요, typedef와 동등한 것은 없습니다. 하나의 파일 내에서 'using'지시문을 사용할 수 있습니다. 예 :

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

그러나 해당 소스 파일에만 영향을 미칩니다. C 및 C ++에서 필자의 경험은 typedef일반적으로 광범위하게 포함 된 .h 파일 내에서 사용되므로 typedef전체 프로젝트 에서 단일을 사용할 수 있다는 것입니다. C # #include에는 using한 파일 의 지시문을 다른 파일에 포함시킬 수 있는 기능이 없으므로 C #에는 해당 기능이 없습니다 .

다행스럽게도 여러분이 제공하는 예제 에는 암시 적 메소드 그룹 변환이라는 수정 사항 있습니다. 이벤트 구독 줄을 다음과 같이 변경할 수 있습니다.

gcInt.MyEvent += gcInt_MyEvent;

:)


11
나는 당신이 이것을 할 수 있다는 것을 항상 잊어 버립니다. Visual Studio가 더 자세한 버전을 제안하기 때문일 수 있습니다. 그러나 핸들러 이름을 입력하는 대신 TAB을 두 번 누르는 것이 좋습니다.;)
OregonGhost

11
내 경험에서 (이는 드물지만) 정규화 된 유형 이름을 지정해야합니다. 예를 들어 : using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; 맞습니까? 그렇지 않으면 using위 의 정의 를 고려하지 않는 것 같습니다 .
tunnuz

3
typedef uint8 myuuid[16];"using"지시문을 통해 변환 할 수 없습니다 . using myuuid = Byte[16];컴파일하지 않습니다. 유형 별칭 using을 만드는 데만 사용할 수 있습니다 . 전체 선언 (배열 크기 포함)에 대한 별칭을 만들 수 있기 때문에 훨씬 유연합니다. 이 경우 대안이 있습니까? typedef
natenho

2
@natenho :별로. 당신이 올 수있는 가장 가까운 것은 아마도 고정 크기 버퍼를 가진 구조체를 갖는 것입니다.
Jon Skeet

1
@tunnuz 네임 스페이스 내에 지정하지 않는 한
John Smith

38

존은 정말 좋은 해결책을 줬습니다.

때때로 내가 의지했던 것은 클래스에서 상속하고 생성자를 만드는 것이 었습니다. 예 :

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

최상의 솔루션은 아니지만 (다른 사람이 어셈블리를 사용하지 않는 한) 작동합니다.


41
확실히 좋은 방법이지만, 성가신 봉인 유형이 존재하고 작동하지 않는다는 것을 명심하십시오. C #이 이미 typedef를 도입하기를 바랍니다. 절실히 필요합니다 (특히 C ++ 프로그래머의 경우).
MasterMastic

1
기본 유형을 상속하지 않고 랩핑하는 LikeType이라는이 상황에 대한 프로젝트를 작성했습니다. 또한 암시 적으로 변환됩니다 에게 당신이 뭔가를 사용할 수 있도록 기본 유형 public class FooList : LikeType<IReadOnlyList<Foo>> { ... }어디서나 당신이를 기대하는 사용 후와 IReadOnlyList<Foo>. 아래의 답변 은 자세한 내용을 보여줍니다.
Matt Klein

3
또한 Foo예를 들어 템플릿 메소드를 전달 하면 유형을 유추하지 않습니다 List<T>. 적절한 typedef를 사용하면 가능합니다.
Aleksei Petrenko

18

수행중인 작업을 알고 있으면 암시 적 연산자로 클래스를 정의하여 별칭 클래스와 실제 클래스 사이를 변환 할 수 있습니다.

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

나는 실제로 이것을 보증하지 않으며 이와 같은 것을 사용하지는 않았지만 이것은 특정 상황에서 효과적 일 수 있습니다.


@palswim에게 감사드립니다. "typedef string Identifier;"와 같은 것을 찾고 있습니다. 그래서 당신의 제안은 내가 필요한 것일 수도 있습니다.
yoyo

6

C #은 이벤트 대리자에 대해 상속 된 공분산을 지원하므로 다음과 같은 방법이 있습니다.

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

명시 적 캐스트가 필요하지 않은 이벤트를 구독하는 데 사용할 수 있습니다.

gcInt.MyEvent += LowestCommonHander;

람다 구문을 사용할 수도 있고 인텔리전스가 모두 수행됩니다.

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

Linq를 잘 살펴보기 위해 진지하게 돌아갈 필요가 있습니다 ...하지만 기록을 위해, 당시에 2.0을 구축했습니다 (VS 2008에서)
Matthew Scharley

또한, 잘 구독 할 수는 있지만 이벤트 인수를 얻으려면 안전한쪽에 있기 위해 명시 적 캐스트가 필요하며 유형 검사 코드가 필요합니다.
Matthew Scharley

9
구문은 정확하지만 "Linq 구문"이라고 말하지는 않습니다. 오히려 람다 식입니다. Lambda는 Linq가 작동하도록 지원하는 기능이지만 완전히 독립적입니다. 기본적으로 대리자를 사용할 수있는 곳이면 람다 식을 사용할 수 있습니다.
Scott Dorman

페어 포인트, 나는 람다를 말해야했다. 델리게이트는 .Net 2에서 작동하지만 중첩 된 제네릭 형식을 명시 적으로 다시 선언해야합니다.
Keith

5

typedef가 없다고 생각합니다. GenericClass에서 제네릭 유형 대신 특정 델리게이트 유형 만 정의 할 수 있습니다.

public delegate GenericHandler EventHandler<EventData>

이것은 더 짧아 질 것입니다. 그러나 다음 제안은 어떻습니까?

Visual Studio를 사용하십시오. 이렇게하면 입력 할 때

gcInt.MyEvent += 

이미 Intellisense의 완전한 이벤트 핸들러 서명을 제공합니다. Tab을 누르면 거기에 있습니다. 생성 된 핸들러 이름을 승인하거나 변경 한 후 Tab 키를 다시 눌러 핸들러 스텁을 자동 생성하십시오.


2
그래, 그게 내가 예제를 생성하기 위해 한 것입니다. 그러나 사실이 여전히 혼란 스러울 수 있습니다.
Matthew Scharley

무슨 뜻인지 알아 그렇기 때문에 이벤트 서명을 짧게 유지하거나 FxCop 권장 사항에서 벗어나 자신의 대리자 유형 대신 Generic EventHandler <T>를 사용하는 것이 좋습니다. 그러나, 짧은 손 버전 스틱 : 존 소총에 의해 제공
OregonGhost

2
ReSharper를 사용하는 경우 긴 버전이 과도하게 사용되었다고 표시되며 (회색으로 채색하여) "빠른 수정"을 사용하여 다시 제거 할 수 있습니다.
Roger Lipscombe

5

C ++과 C # 모두 기존 형식과 의미가 동일한 형식 을 만드는 쉬운 방법이 없습니다 . 나는 타입 안전 프로그래밍에 꼭 필요한 'typedefs'를 발견하고 실제 수치는 c #에 내장되어 있지 않습니다. 의 차이 void f(string connectionID, string username)로는 void f(ConID connectionID, UserName username)분명하다 ...

(BOOST_STRONG_TYPEDEF를 높이면 C ++에서 비슷한 것을 얻을 수 있습니다)

상속을 사용하고 싶을 수도 있지만 몇 가지 중요한 제한이 있습니다.

  • 기본 유형에는 작동하지 않습니다
  • 파생 된 유형은 여전히 ​​원래 유형으로 캐스트 될 수 있습니다. 즉, 원래 유형을받는 함수에 보낼 수 있습니다.
  • 봉인 된 클래스에서 파생 될 수 없습니다 (즉, 많은 .NET 클래스가 봉인 됨)

C #에서 비슷한 것을 달성하는 유일한 방법은 새 클래스에서 형식을 작성하는 것입니다.

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

이것이 효과가 있지만 typedef에 대해서는 매우 장황합니다. 또한 Composed 속성을 통해 클래스를 직렬화하려는 경우 직렬화 (예 : Json)에 문제가 있습니다.

아래는 "재귀 적으로 반복되는 템플릿 패턴"을 사용하여 훨씬 간단하게 만드는 도우미 클래스입니다.

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

}

Composer를 사용하면 위 클래스가 간단 해집니다.

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

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

또한 SomeTypeTypeDef의지는 동일한 방식으로 Json으로 직렬화됩니다 SomeType.

도움이 되었기를 바랍니다 !


4

오픈 소스 라이브러리와 내가 만든 LikeType 이라는 NuGet 패키지 를 사용하면 원하는 GenericClass<int>동작을 얻을 수 있습니다 .

코드는 다음과 같습니다.

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

LikeType은 stackoverflow.com/questions/50404586/… 와 같은 복잡한 작업에 적합 합니까? 나는 그것을 가지고 놀았지만 작동하는 수업 설정을 얻을 수 없습니다.
Jay Croghan

그것은 실제로 LikeType도서관 의 의도가 아닙니다 . LikeType의 기본 목적은 Primitive Obsession 을 돕는 것이므로 래퍼 유형과 같이 랩핑 된 유형을 전달할 수 있기를 원하지 않습니다. 에서와 같이 Age : LikeType<int>함수에을 필요로하는 경우 Age호출자가을 전달 Age하지 않고을 전달하고 싶습니다 int.
매트 클라인

즉, 귀하의 질문에 대한 답변이 있다고 생각합니다.
매트 클라인

3

여기에 코드가 있습니다. 즐기십시오! 네임 스페이스 줄 내부의 "using"문을 dotNetReference 유형에서 선택했습니다. 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

봉인되지 않은 클래스의 경우 단순히 클래스에서 상속합니다.

public class Vector : List<int> { }

그러나 봉인 클래스의 경우 이러한 기본 클래스를 사용하여 typedef 동작을 시뮬레이션 할 수 있습니다.

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

typedefC #에서 찾은 가장 좋은 대안 은 using입니다. 예를 들어 다음 코드를 사용하여 컴파일러 플래그를 통해 부동 소수점 정밀도를 제어 할 수 있습니다.

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

불행히도, 이것을 사용하는 모든 파일 의 맨 위에 배치 해야합니다 real_t. 현재 C #에서 전역 네임 스페이스 유형을 선언하는 방법은 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.