2

I'm considering various advice I've seen about a solution for C#'s lack of typedef, that is to create a struct with one member of the type you want to "typedef". For example:

struct SpecialIndex {
  private int value;
  public SpecialIndex(int value) {
    this.value = value;
  }

  // implement necessary operations: add, IEquatable, increment, etc.
  //...
}

List<SpecialThing> specialThings = new List<SpecialThing> { ...... };

SpecialThing GetSpecialThingByIndex(SpecialIndex idx) {
  return specialThings[idx.value];
}

void Main() {
  SpecialIndex myThingIndex = new SpecialIndex(3);
  int someInt = 5 + 12;
  
  SpecialThing thing1 = GetSpecialThingByIndex(myThingIndex); // nice
  SpecialThing thing2 = GetSpecialThingByIndex(someInt); // woah sonny, what makes you think this is an index?
}

Putting aside the potential awkwardness of properly implementing such a struct and assuming its internals were up-to-snuff, what are the both the technical and usability gotchas of such an approach?

Thanks

Ipsquiggle
  • 1,814
  • 1
  • 15
  • 25
  • 2
    I'm curious what's your use-case for something like this. I know there are a few (for instance, there's no "numeric" interface in .Net which got a lot of people do a lot of hard work over the years) - but what's your reason? – Zohar Peled Sep 03 '20 at 20:38
  • 1
    The analogous C# feature is `using SoneAlias = SomeType;`. The semantics of your wrapper are completely different. – Aluan Haddad Sep 03 '20 at 20:59
  • A practical use case of such an struct is to implement something like this: https://stackoverflow.com/q/5377237/2557263 – Alejandro Sep 03 '20 at 21:01
  • 1
    I'm confused about what you're asking. Seems like you understand a lot of the gotchas; you have to implement all of the operators, interfaces, etc. you'd get for free with `int`, on your `SpecialIndex` type. It sounds like you want someone to list them all out for you. – Heretic Monkey Sep 03 '20 at 21:03
  • _"what are the both the technical and usability gotchas of such an approach?"_ -- that is, frankly, far too broad a question for Stack Overflow. There is no end to the number of different answers that might be considered responsive to such a question, and no clear way to distinguish which answer you want. That said, see duplicate for discussion of the key factors here. See also the very-closely-related https://stackoverflow.com/questions/13081961/is-it-possible-to-create-system-int32-in-c – Peter Duniho Sep 03 '20 at 22:40
  • This is exactly how `DateTime` works. It effectively encapsulates `private ulong dateData;` inside a `struct`. – Enigmativity Sep 03 '20 at 22:55

1 Answers1

4

I have used this struct wrapper approach in a couple of places and always got mixed results. I'm afraid that this is always use-case specific and that you will have to try and see how it works for you. What follows is my mileage.

If you have for example Dictionary<string, string>, create a couple of types and get Dictionary<PropertyName, PropertyValue>, then that works excellent for readability and usability, especially if you do some LINQ queries with such types. So this is a use-case I can recommend and a place where it worked for me: to provide a more precise name for a set of values that you don't want to mix up, especially if the underlying type is identical.

It has, however, only limited value for correctness as the wrapped values usually do come from outside of your system (otherwise you would use enum, inheritance or redesigned your API, right?). So in the end, you only move the value validation from the actual index access to the constructor of the wrapper type. You might think that you are getting something from the type system, but ultimately, your wrapper type still accepts int and the consumer of your API has to read the docs on what the constraints of this int are. But instead of visiting the API they want to use, they now need to navigate one place further and memorize additional type. For this reason I have so far had negative experience in defining special index types this way.

Just a nitpick, a typedef would not resolve your issue as it creates a type alias similar to C#'s using - i.e. no new type is created, the types are still compatible, only have different names. What you're looking for is newtype as in Haskell.

Zdeněk Jelínek
  • 2,611
  • 1
  • 17
  • 23