Kevin Carrasco
Kevin Carrasco

Reputation: 1052

How to use generics to pass argument to a non-generic method?

Why does the following code not compile? How can I create a generic method which calls the appropriate "BitConverter.GetBytes" overload based on if the generic type is an "int", "bool", "char", etc.? More generally, how can I create a generic method which calls a non-generic method based on the generic parameter's type?

using System;

public class Test
{
    public static void Main()
    {
      var f = new Foo();
      f.GetBytes(10); // should call BitConverter.GetBytes(int);
      f.GetBytes(true); // should call BitConverter.GetBytes(bool);
      f.GetBytes('A'); // should call BitConverter.GetBytes(char);
    }
}

public class Foo
{
    public byte[] GetBytes <TSource> (TSource input)
    {
      BitConverter.GetBytes(input);
    }
}

Upvotes: 6

Views: 3071

Answers (7)

Ryan Mann
Ryan Mann

Reputation: 5357

If you are willing to take a performance hit, you can use reflection and an extension to object called GetBytes. example....

public static class Extensions
{
    #region Fields
    public static Type bcType;
    #endregion

    #region Constructor
    static Extensions()
    {
        bcType = typeof(BitConverter);
    }
    #endregion
    public static byte[] GetBytes(this object value)
    {
        Type typeObj = value.GetType();
        MethodInfo miGetBytes = bcType.GetMethod("GetBytes", new Type[] { typeObj });
        if (miGetBytes == null)
            throw new InvalidOperationException("Method: GetBytes on BitConverter does not have an overload accepting one paramter of type: " + typeObj.FullName);
        byte[] bytesRet = (byte[])miGetBytes.Invoke(null, new object[] { obj });
        return bytesRet;
    }
}

So GetBytes accepts an object. Then it get's it's type and tries to get the MethodInfo from BitConverter based on the type of object passed in. If it can't find an overload that accepts that type as a parameter it throws an InvalidOperation Exception. If it does, it calls it passing in the instance of obj as the value and returns the byte array.

E.g. Usage code,

//make sure the extensions namespace is defined where this code is run.
Console.WriteLine(((ushort)255).GetBytes().ToBase64());
Console.WriteLine(10.0.GetBytes().ToBase64());
Console.WriteLine(((int)2000000000).GetBytes().ToBase64());
Console.WriteLine(((short)128).GetBytes().ToBase64());
//Below causes an error
Console.WriteLine("cool".GetBytes().ToBase64()); //because BitConvert.GetBytes has no overload accepting an argument of type string.

Upvotes: 1

ClickRick
ClickRick

Reputation: 1568

Given that there are "only" 10 overloads of BitConverter.GetBytes, it's not impossible to reflect them all explicitly like this:

public class Foo
{
    public byte[] GetBytes(bool input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(char input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(double input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(float input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(int input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(short input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(long input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(uint input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(ulong input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(ushort input) { return BitConverter.GetBytes(input); }
}

It's not generic (which you had asked for), and isn't scalable to more complex examples, but if the numbers are small then it's an approach to consider.

Upvotes: 1

Mark Cidade
Mark Cidade

Reputation: 100047

Your code doesn't compile because the compiler can't verify that any type for TSource will be accepted by BitConverter.GetBytes(). You can check for each type individually and cast:

public byte[] GetBytes <TSource> (TSource input)
{
    var t = typeof(TSource);
    return    (t == typeof(int))  ? BitConverter.GetBytes((int) (object) input)
            : (t == typeof(bool)) ? BitConverter.GetBytes((bool)(object) input)
            : (t == typeof(char)) ? BitConverter.GetBytes((char)(object) input)
            : null;
}

Upvotes: 0

Paul Ruane
Paul Ruane

Reputation: 38620

Where the code is calling BitConverter.GetBytes, the type is TSource, so the call can't be statically bound by the compiler. You can work around this using dynamic invocation, meaning it'll compile fine and then get resolved at runtime:

…
public byte[] GetBytes(dynamic input)
{
    return BitConverter.GetBytes(input);
}

You will pay a performance penalty for using dynamic invocation and if no suitable method to call is available you will get a runtime exception.

Upvotes: 1

Lee
Lee

Reputation: 144206

The reason this doesn't work is that generic methods still resolve calls to methods made within them statically. Since TSource could be any type at all, it can only call a method on BitConverter which takes an object argument. Since none exists, the compilation fails.

The only way to get the behaviour you want to is to use dynamic:

public byte[] GetBytes <TSource> (TSource input)
{
    BitConverter.GetBytes((dynamic)input);
}

although the generic parameter is now redundant and you have no type safety.

In this situation you can either create a number of matching overloads e.g.

public byte[] GetBytes(bool b) { ... }
public byte[] GetBytes(int i) { ... }

or take a Func<T, byte[]> argument and wrap each BitConverter method you need e.g.

public void DoSomething<T>(T input, Func<T, byte[]> f)
{
    byte[] bytes = f(input);
    //handle bytes
}
DoSomething(true, BitConverter.GetBytes);

which may give you more flexibility.

Upvotes: 1

Timothy Shields
Timothy Shields

Reputation: 79621

You'll need to use reflection to do that.

  1. Get the GetBytes method group from the BitConverter static type.
  2. Pull out the overload for which the first parameter has type TSource.
  3. Call that specific method via the Invoke method.

If you aren't familiar with some of this, I can expand the answer with code for those steps.

Edit: Or just use dynamic like others are suggesting and save yourself some work.

Upvotes: 0

Reed Copsey
Reed Copsey

Reputation: 564891

More generally, how can I create a generic method which calls a non-generic method based on the generic parameter's type?

In general, you can't, unless the method in question takes System.Object as a parameter. The problem is that the generic isn't constrained to just types that would be allowed by the method call arguments.

The closest you can do is to use runtime binding:

public byte[] GetBytes <TSource> (TSource input)
{
     dynamic obj = input;
     BitConverter.GetBytes(obj);
}

This pushes the method binding logic to runtime, and will throw if there isn't an appropriate method to call.

Upvotes: 8

Related Questions