Let’s say I have a method that I want to make generic, and so far it had a big switch case of types.

For an simplified example,

switch (field.GetType()) {
case Type.Int: Method((int)x)...
case Type.NullInt: Method((int?)x)...
case Type.Long: Method((long)x)...

I’d like to be able to just call my GenericMethod<T>(field) instead and I’m wondering if this is possible and how would I go around doing it.

GenericMethod(field)

public void GenericMethod<T>(T field)

Can I use reflection to get a type and the pass it into the generic method somehow, is it possible to transform Type into <T>?

Can I have a method on the field object that will somehow give me a <T> type for use in my generic method?

Sorry for a confusing question, I’m not really sure how to phrase it correctly, but basically I want to get rid of switch cases and lots of manual coding when all I need is just the type (but that type can’t be passed as generic from parent class)

  • TwilightKiddy@programming.dev
    link
    fedilink
    arrow-up
    5
    ·
    edit-2
    4 months ago

    I don’t believe the first code sample is a valid C# code. Switch-case statements expect constants for case conditions and runtime types are not constants, obviously. What you can do is something like this:

    void MyMethod(object arg)
    {
        if (arg.GetType() == typeof(int))
            MyTypedMethod((int)arg);
        else if (arg.GetType() == typeof(int?))
            MyTypedMethod((int?)arg);
        else if (arg.GetType() == typeof(long))
            MyTypedMethod((long)arg);
    }
    

    This will work, but the problem here is that inheritance will fuck you up. All the types we check in this example are sealed and we don’t really have to worry about it in this case, but generally this code is… just bad.

    And yes, the correct way of implementing something like this is by using generic types. Starting from C# 7.0 we have this cool structure:

    void MyMethod<T>(T arg)
    {
        switch (arg)
        {
            case null:
                // Do something with null
                break;
            case int intArg:
                // Do something with intArg
                break;
            case long longArg:
                // Do something with longArg
                break;
        }
    }
    

    You’ll have to check for null separately, as you can’t use Nullable<> there, but overall, it looks much cleaner and handles inheritance properly for you.

    This is called pattern matching, you can read more about it here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns

    • Cyno@programming.devOP
      link
      fedilink
      arrow-up
      1
      ·
      edit-2
      4 months ago

      The switch case was based on an enum but it is what I want to get rid of. In the end I ended up doing what you wrote there, expect instead of casting I’m just writing

      case Type.Int: return MyTypedMethod<int>(args)
      case Type.IntNull: return MyTypedMethod<int?>(args)
      // etc for another 10+ different types
      

      It just feels like I’m doing something wrong if i have to manually handle every case in a switch (or if else) statement and I was wondering how could I write, for example, a method that would do the conversion from Type.Long to System.Int64 for me, and then I just pass that type into the generic method instead of having to manually translate it into a type every time it is used.

      However, if I have to use reflection maybe hardcoding it manually every time is actually faster and easier to debug so maybe i’m just overthinking it.

      That c# 7 structure looks interesting but not sure it solves my issue, I need to get to the part where i have the generic type T in the first place. I dont know how to get a “T” out of a custom field indicating type, so to speak.

      edit: as for the invalid code, i just wrote it quickly as example but you are right. Pretend it says switch (field.SomeType) instead of it being a method

      • if i have to manually handle every case in a switch (or if else) statement and I was wondering how could I write, for example, a method that would do the conversion

        You could still do it that way with a switch. Only the case part needs to be constant…

        ` switch (field.GetType().ToString()) {

        case “Int”: Method((int)x)…

        case “NullInt”: Method((int?)x)…

        case “Long”: Method((long)x)… `

        Been a while since I last did this though - you may need to do string caseType=field.GetType().ToString() first, then do switch(caseType). I think from memory you can do it the other way though.

        P.S. I clicked on “code” (which just starts/ends with an apostrophe), but it doesn’t want to display as code - I don’t know why

  • theit8514@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    4 months ago

    If you don’t want to go the pattern matching route you can also use reflection with MakeGenericMethod to specify the generic type and then invoke it.

    Untested example:

    var type = field.GetType();
    var methodInfo = typeof(GenericClass).GetMethod("GenericMethod").MakeGenericMethod(type);
    methodInfo.Invoke(null, field);
    

    Edit: wrong documentation link https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodinfo.makegenericmethod?view=net-8.0 ~~https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.methodbuilder.makegenericmethod?view=net-8.0~~

    • Cyno@programming.devOP
      link
      fedilink
      arrow-up
      2
      ·
      4 months ago

      I have this instinct drilled into me for years that anything using reflection is bad, both in terms of performance or code clarity/ease of debugging. Your answer is correct though, I could make a generic method using reflection… now I’m just not sure if it’s better to just manually hardcode the cases for all types anyway

      • theit8514@lemmy.world
        link
        fedilink
        arrow-up
        1
        ·
        edit-2
        4 months ago

        Yes, I agree that this is a bit of an anti-pattern, as you lose quite a few benefits from the Generics compile-time safety and instead open yourself to runtime exceptions. Not sure what your use case is, but if you want to maintain type safety it might be better to have multiple overloads for each type you want to process rather than a Generic. Typically you use Generics when the actual type doesn’t matter to the method being called (e.g. LINQ uses Generics for IEnumerable<T>.Where because T can be anything and it just calls a Func<T, bool> on each element).

  • GetOffMyLan@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    4 months ago

    You can actually use the dynamic type here for late binding of overloads. You will get a runtime error for unknown types though.

    You essentially define the function overloaded for each type. Then call the function with a dynamic and the runtime will figure out what method to call

  • DrDeadCrash@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    4 months ago

    Here’s a real world side project example of how I handle this situation:

     public IResult<T> GetResourceValue<T>(string path)
        {
            string err = $"{typeof(T).FullName} is not available from this Resource ({nameof(FileSystemResource)})";
    
            switch (typeof(T))
            {
                case Type t when t == typeof(DriveInfo):
                    return (IResult<T>)new Ok<DriveInfo>(BackingValue);
                case Type t when t == typeof(DirectoryInfo):
                    err = $"Directory path invalid: {path}";
                    var dir = new DirectoryInfo(path);
    
                    if (dir.Exists)
                        return (IResult<T>)new Ok<DirectoryInfo>(dir);
                    break;
                case Type t when t == typeof(FileInfo):
                    err = $"File path invalid: {path}";
                    var file = new FileInfo(path);
    
                    if (file.Exists)
                        return (IResult<T>)new Ok<FileInfo>(file);
                    break;
            }
            return new Error<T>(err);
        }
    

    You said elsewhere that it feels like you’re doing something wrong if you have to check for every type just to use a Generic. I think you’re right in thinking along those lines. There should be a minimal number of types to check, and Ideally limited via a type constraint.

    Here’s example that includes a constraint:

        public IResult<T> GetValue<T>() where T : struct =>
            typeof(T) switch
            {
                Type t when t == typeof(int) && value <= int.MaxValue =>
                 (IResult<T>)new Ok<int>((int)value),
                Type t when t == typeof(uint) && value <= uint.MaxValue && value >= uint.MinValue =>
                 (IResult<T>)new Ok<uint>((uint)value),
                Type t when t == typeof(byte) && value <= byte.MaxValue && value >= byte.MinValue =>
                 (IResult<T>)new Ok<byte>((byte)value),
                Type t when t == typeof(sbyte) && value <= (int)sbyte.MaxValue =>
                 (IResult<T>)new Ok<sbyte>((sbyte)value),
                Type t when t == typeof(short) && value <= (int)short.MaxValue =>
                 (IResult<T>)new Ok<short>((short)value),
                Type t when t == typeof(ushort) && value <= ushort.MaxValue =>
                 (IResult<T>)new Ok<ushort>((ushort)value),
                Type t when t == typeof(long) && value <= long.MaxValue =>
                 (IResult<T>)new Ok<long>((long)value),
                Type t when t == typeof(ulong) => (IResult<T>)new Ok<int>((int)value),
                _ => new IntegerError<T>()
            };