1
public static object GetObject(int x)
{
    return new object { };
}
public static object GetObject(string x)
{
    return new object { };
}
public static void ProcessObject<T>(T x) where T : int, string <= got error here: 
{
    object o = GetObject(x);
}

Got error "A type used as a constraint must be an interface, a non-sealed class or a type parameter."

How can I rewrite the code to get it work without write ProcessObject(int x) and ProcessObject(string x) twice?

Eric Yin
  • 8,737
  • 19
  • 77
  • 118

5 Answers5

3

So what you have now (according to accepted answer and your comments) is:

public static void ProcessObject<T>(T x)
{
    object o;
    if (typeof(T) == typeof(int))
        o = GetObject((int)(object)x);
    else if (typeof(T) == typeof(string))
        o = GetObject((string)(object)x);
    else
        throw new Exception();
    // do stuff with o
}

I'd recommend making public int and string overloads, but to prevent code duplication, internally call another method:

public static void ProcessObject(int x)
{
    ProcessObject(GetObject(x));
}
public static void ProcessObject(string x)
{
    ProcessObject(GetObject(x));
}
private static void ProcessObject(object o)
{
    // do stuff with o
}

This makes your public methods' input values clear: int and string are the only acceptable types, while still not duplicating your actual logic (// do stuff with o).

You might dislike that the two public ProcessObject methods are duplicates of each other, (on the surface anyway; under the hood, they're calling two different GetObject overloads) but I think it's the best option.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
2

You cannot do what you are trying to do: first, it is not possible to list several classes in a generic constraint; second, the type that you can put in a constraint must be such that you could inherit it (or implement it if it is an interface). Both int and string fail this check. In cases like this, you would be better off with two separate overloads.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • the only way I know how to make it work is to write `ProcessObject(string x)` and `ProcessObject(int x)` with identical code. Which what I want is to simple write once, is there anyway to just write once – Eric Yin Aug 04 '12 at 12:03
  • How are you planning on using `x` that the code would be the same in each? – Tim S. Aug 04 '12 at 12:03
  • @tims, it will not same, one is String, another is Int32. But I have overload function `GetObject()` with get int and string, then the following code becomes identical – Eric Yin Aug 04 '12 at 12:04
  • 1
    @EricYin Another alternative would be to make your `GetObject` generic, and deal with `int` and `string` inside the function. – Sergey Kalinichenko Aug 04 '12 at 12:04
  • 1
    @EricYin Yes - you can do something like `if (typeof(T) == typeof(string))`. It isn't pretty, mostly because a compile-time check is replaced with a run-time check. – Sergey Kalinichenko Aug 04 '12 at 12:07
  • @dasblinkenlight sure, will do that way, even not pretty, but better than write code twice, in my real code, `ProcessObject()` is quite complex, I do not want to maintain two copies – Eric Yin Aug 04 '12 at 12:08
  • @EricYin: Also note that you'll have to handle any `T` that is *not* either `int` or `string` yourself, as the compiler doesn't prevent anyone from invoking e.g. `ProcessObject(42.5);` – O. R. Mapper Aug 04 '12 at 12:22
  • 1
    @EricYin: Moreover, please check whether you really need a generic method in the first place. A generic method without any type constraints, only one argument and no return value is much like a method that simply accepts an `object` argument. – O. R. Mapper Aug 04 '12 at 12:23
1

Just remove the where part

public static void ProcessObject<T>(T x) 
{
    object o = GetObject(x);
}

And also don't use object in your other methods, instead use T

oleksii
  • 35,458
  • 16
  • 93
  • 163
  • then Got Error `cannot convert from 'T' to 'string'` and `The best overloaded method match for 'GetObject(string)' has some invalid arguments` – Eric Yin Aug 04 '12 at 12:01
  • 2
    @EricYin you shall rewrite your `object GetObject(string x)` to `T GetObject(T x)` or something similar. You shall not use `object` at all. Maybe rename the method to something like `T GetItemBy(object id)`. – oleksii Aug 04 '12 at 12:08
1

it's impossible in C# take a look on Constraints on Type Parameters. Try to use dynamic

GSerjo
  • 4,725
  • 1
  • 36
  • 55
1

Generally speaking, if your object reacts differently based on the generic type argument, you probably shouldn't be using generics in this case. Generics are great for situations where you want to do always the same thing, no matter what the actual type used.

Therefore, generic constraints will only allow you to list one base class for a type argument. Any actual types passed to the respective type arguments are meant to be a part of the given inheritance hierarchy starting with the base class you specified, so users of your class can specify any type that matches the base class or any of its subclasses.

At the same time, you, the author of the generic class, can safely assume that the specified type has (at least) the interface of the base class indicated by the constraint. Hence, you may access any members of the base class.

If you want to allow either string or int, imagine what members that could be. Both are derived directly from System.Object, hence the restriction would make no sense as it is no restriction; every type is derived from System.Object.

Summarizing, if you really want to treat string and int differently, this is definitely a case for offering two overloads rather than one generic class.

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • I am currently write two overloads on `GetObject()` to deal with `int` and `string` differently. But in function `ProcessObject()` since the data back from `GetObject()` always the same, so I do not want to write two overloads for `ProcessObject()`, to avoid maintain two copies of the same code. How? – Eric Yin Aug 04 '12 at 12:24
  • 1
    @EricYin: The most straightforward solution would be to just drop the generic method argument and have the method accept an `object`, then evaluate that object's `GetType()` value as described in the comments for the accepted answer. – O. R. Mapper Aug 04 '12 at 12:25
  • 1
    @EricYin: If you want to make your API type-safe nonetheless, make `ProcessObject(object)` private and offer two public methods `ProcessObject(int)` and `ProcessObject(string)` which just call the `object` overload. Like that, no-one can insert something that is not an `int` or a `string` when calling your method. – O. R. Mapper Aug 04 '12 at 12:27
  • @ORMapper in that case, how to covert `object` back to `int`, I got error `The as operator must be used with a reference type or nullable type ('int' is a non-nullable value type)` on `t as int` or `(int)t` – Eric Yin Aug 04 '12 at 12:30
  • 1
    @EricYin: With casting after doing the type-check: `if (x.GetType() == typeof(string)) GetObject((string)x) else if (x.GetType() == typeof(int)) GetObject((int)x);` – O. R. Mapper Aug 04 '12 at 12:32