1

I have a system where every interface/class has two codes (InterfaceCode, SubtypeCode) - this is for efficiently saving and re-generating objects from hex-strings.

I now have a specific usecase where I need a specific object of a class which inherites Iitem. To check if the ItemCode matches the required interface/class I wanted to compare values with T.InterfaceCode and T.SubtypeCode (see below). Sadly this won't work because I cannot access members of a type parameter (CS0704).

public static T AsType<T>(string ItemCode) where T : Iitem
{
    if (ItemCode.Length < 5) return null;

    ItemCodeDes itemCode = new ItemCodeDes(ItemCode);

    if(itemCode.InterfaceCode != T.InterfaceCode) return null; //Error CS0704 Cannot do non-virtual member lookup in 'T' because it is a type parameter
    
    if(itemCode.SubtypeCode != T.SubtypeCode) return null; //same here...

    //...
}

with

public static ushort SubtypeCode { get; } = 0;
public static ushort InterfaceCode { get; } = 0;

I really don't wont to hardcode Lookup Tables for each class (there will be about 100-200).

Is there a good & clean solution to my problem (without using Reflection)?

Edit: I am still using C# .NET Core 3.1

For those who need more context:

public class Iitem
{
    public static ushort SubtypeCode { get; } = 0;
    public static ushort InterfaceCode { get; } = 0;
    public Iitem(string itemName, string description, ItemTier tier, ushort id)
    {
        Name = itemName;
        Tier = tier;
        Description = description;
        Id = id;
    }

    public string Name { get; }
    public string Description { get; }
    public ItemTier Tier { get; }
    public ushort Id { get; }

    /// <summary>
    /// Gibt den itemspezifischen Code zurück.
    /// </summary>
    /// <returns>Itemcode als Hex-String</returns>
    public virtual string GetItemCode()
    {
        return GenerateItemCode(0, 0);
    }
    internal string GenerateItemCode(ushort Interface, ushort Subtype, ulong Generationspecifics = 0)
    {
        return Interface.ToString("X1") + Subtype.ToString("X1") + Id.ToString("X3") + (Generationspecifics != 0 ? Generationspecifics.ToString("X") : "");
    }

    /// <summary>
    /// Generiert einen String mit den Stats und einer Beschreibung des Items.
    /// </summary>
    /// <returns></returns>
    public virtual string PrintItem()
    {
        return Name + " (" + Tier.ToString() + ") - " + Description;
    }

    public enum ItemTier
    {
        Common,
        Uncommon,
        Epic,
        Legendary,
        Heavengrade,
        Forbidden,
    }
}
private class ItemCodeDes
    {
        public ItemCodeDes(string ItemCode)
        {
            if (ItemCode.Length < 5) return;

            InterfaceCode = ushort.Parse(ItemCode[0].ToString(), System.Globalization.NumberStyles.HexNumber);
            SubtypeCode = ushort.Parse(ItemCode[1].ToString(), System.Globalization.NumberStyles.HexNumber);
            ItemId = ushort.Parse(ItemCode.Substring(2, 3), System.Globalization.NumberStyles.HexNumber);

            if (ItemCode.Length > 5)
                Generationspecifics = ItemCode.Substring(5);
        }
        public ushort InterfaceCode { get; }
        public ushort SubtypeCode { get; }
        public ushort ItemId { get; }
        public string Generationspecifics { get; } = "";
    }
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
MP9
  • 45
  • 5
  • You would presumably have to use Reflection. – jmcilhinney May 16 '23 at 14:52
  • 1
    Can you please provide a [mre]? – Guru Stron May 16 '23 at 14:54
  • @GuruStron There should be everything you need to reproduce the "Problem". [CS0704](https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0704?f1url=%3FappId%3Droslyn%26k%3Dk(CS0704)) – MP9 May 16 '23 at 14:57
  • 1
    @MP9 there isn't, please provide actual code for the `Iitem` interface. You probably want to use `static abstract` members in the interface (added in C# 11). – vgru May 16 '23 at 15:00
  • What is `Iitem`, what is `ItemCodeDes`? What is `InterfaceCode` and `SubtypeCode`? There is definitely not enough info to copy-paste the code and repro the issue. – Guru Stron May 16 '23 at 15:01
  • 1
    You need `static abstract`. [This works](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmARgFgAoagYQDoAVGAZwwB5m2BJDGAWwB8ACgCUAbmrUiAZgAEpObWoBvanI0L5RMkgUo5XDoxGi5AdwAWMWIbkg53AJZ9+6zWqqbvCsgE5hRnpuPD4oADMAQzAYWggAExgJdw0AX2p0mipZOSdQmyiYxxcBVRStXz1I4DYoaIw5HBZLaAaAZRxgDABPAAdYhJg5FTkAcxgMcTlM7xydKpqMOrAGppaoBpCwwoHE4bGJqczM6W0SQ1YMXgF7Ytcyr0053Ubm1rkOrr7doZHxyemcgAvHIAAySR4aZ56NbvLYFaI/fb/I7AsEQ1JAA===). [See here](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/static-abstracts-in-interfaces?source=recommendations) – canton7 May 16 '23 at 15:03
  • Possibly you need to use newly introduced `static abstract` interface members. But I'm not sure. – Guru Stron May 16 '23 at 15:04
  • 2
    Wait, `Iitem` is a _class_ ? – Fildor May 16 '23 at 15:05
  • 2
    Although, I would never have guessed that `Iitem` was a class!! Static abstract properties only work on interfaces, so you might have to refactor – canton7 May 16 '23 at 15:05
  • @Fildor yes it sadly is. I tried to change it to an interface, but it breaks so much – MP9 May 16 '23 at 15:07
  • 3
    (And this is why it's important to include a full MRE, even if *you* think you've included everything!) – canton7 May 16 '23 at 15:08
  • @canton7 true that, sorry for the inconvenience – MP9 May 16 '23 at 15:09
  • Start feeling like always bumping into walls? _Maybe_ it's time to take a step back and rethink some architecture ... (hate to say this). – Fildor May 16 '23 at 15:09
  • Would it be better to create an interface "below" `Iitem` containig `SubtypeCode` and `InterfaceCode`? – MP9 May 16 '23 at 15:11
  • Really hard to tell. This seems to be one instance of when you pull a little string, the whole sweatshirt falls apart. And I personally would question that "efficiently saving and re-generating objects from hex-strings". – Fildor May 16 '23 at 15:15
  • @Fildor I can save (and transport) every object with 5 characters (+additionals for special stuff) instead of saving every object to a json with 100+ variables. – MP9 May 16 '23 at 15:18
  • Maybe you can "save" it if you can upgrade from 3.1 ... is that an option? – Fildor May 16 '23 at 15:20
  • @Fildor yeah, I think I will do that. It really pains me because I wanted to wait for the next long term supported "plattform" to migrate, but I guess I don't have any better options. – MP9 May 16 '23 at 15:22

1 Answers1

2

Is there a good & clean solution to my problem (without using Reflection)?

There is, but it will require you to migrate to latest language version. This will enable option to use static abstract members in interfaces. Introduce corresponding interface and then use it:

public interface IIitem // probably you will need to use better name
{
    public static abstract ushort SubtypeCode { get; } 
    public static abstract ushort InterfaceCode { get; } 
}

public class Iitem : IIitem
{
   // ...
}

And constrain the generic parameter:

public static T AsType<T>(string ItemCode) where T : Iitem, IIitem
{
   // ...
}

If you can't upgrade to support the new language version then you will need to use reflection or refactor the approach (if I correctly understand the case - I usually used in similar case attributes on types and build static dictionaries mapping from type to codes and vise versa, hence "caching" reflection).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I feel like if you opt to use (runtime / on-the-fly, not only one-time-on-start-up) reflection, you can kiss "this is for _efficiently_ saving and re-generating objects from hex-strings" good-bye. – Fildor May 16 '23 at 15:17
  • @Fildor you can "cache" reflection. For example like [here](https://stackoverflow.com/a/62454454/2501279). Or in this case static dictionaries might work (need to see more code). – Guru Stron May 16 '23 at 15:19
  • 1
    At least, you'd need to do some "magic". That's what I meant. Naive reflection won't cut it, I guess. – Fildor May 16 '23 at 15:24
  • 1
    I upgraded to the latest language version and implemented the interface like you suggested. Thank you very much! – MP9 May 16 '23 at 15:56
  • @MP9 glad it helped! Small P.S. - C# generics are not templates (if you come from C++ background - check out [this doc](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/differences-between-cpp-templates-and-csharp-generics)) – Guru Stron May 16 '23 at 16:16