1

I have generated an extension method to display a IList<T> in a meaningful manner:

public static class IListExtension
{
  public static string ToDisplay<T>(this IList<T> source, string seperator = ",")
  {
    string display = string.Empty;
    // create the display string from the elements of this list
    return display;
  }
}

To use this extension I can do this:

IList<someClass> myList = CreateList();
myList.ToDisplay();

My problem is now, that I have a method that gets an object, which also can be a list. So, if the object is no list, I want to use the ToString() method, if it IS a list, I want to use my extension.

public string CreateDisplayString(object element)
{
  if (element == null)
    return string.Empty;

  if (element as IList<T> != null)
    return (element as IList<T>).ToDisplay();

  return element.ToString();
} 

Obviously, the code above does not work, because I cannot cast to the generic IList<T>. If I use an existing class, like IList<string> it works for lists of strings like I want it to, but of course I don't want to create a statement for each possible IList<[name of class]>. Is there a way to use the extension method or any other means to get a custom string from a list of any type?

Jamiec
  • 133,658
  • 13
  • 134
  • 193
Aaginor
  • 4,516
  • 11
  • 51
  • 75
  • 3
    Its a chicken/egg thing. Without the `T` you dont know what to cast it to, and without knowing what to cast it to you cant cast it. Could you instead add your extension to the non-generic `IList` (which is implemented by `List`)? I guess the answer to that depends on the implementation where you have `create the display string from the elements of this list` in your question! – Jamiec Jul 04 '17 at 11:30

6 Answers6

2

Well, you're basically looking for run-time overload resolution - dynamic does that. Have a separate method for the separate overloads you want, and it will pick the right one based on the usual overload resolution rules:

void Do<T>(IList<T> list) => ...
void Do(string someString) => ...
void Do(object somethingElse) => ...

Then you can call the best overload (at run-time) just by doing

Do((dynamic)someObject);

If you can afford to use dynamic, this is probably the simplest solution.

If not, you can exploit type variance. Instead of IList<T>, you can use IEnumerable<T> (which seems to be enough for the cases you've shown), which is covariant with respect to T. This allows you to do

if (someObject is IEnumerable<object> e)
{
  e.ToDisplay();
}

This is only useful if the ToDisplay method doesn't actually use the type argument for something, of course.

Also, as InBetween correctly noted, value types do not support variance - so this will still fail for List<int>, for example - (new List<int>() is IEnumerable<object>) == false. It will only work correctly for reference types. You can still use the non-generic IList for that case, but you'll need a separate ToDisplay method for that. Alternatively, you could use reflection to invoke the correct method with the correct generic type arguments directly, but by that point you'd just have something that works pretty much like dynamic, but clunkier.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • The `IEnumerable` "hack" will fail if `T` is a value type; no variance allowed when it comes to structs. – InBetween Jul 04 '17 at 11:51
  • Thanks a lot for your comprehensive answer! I never used dynamic before, so I'll have a look into it. I think the best and easiest solution is to use the IList for the extension method, because I just use the .ToString() method to build the display. – Aaginor Jul 05 '17 at 07:25
1

You basically have two choices: switch to using IEnumerable instead of IList<T>, as every IList<> is also an IEnumerable. You can then use as and is to determine which code path to take, casting as necessary to call the appropriate method.

Your other option is to use dynamic method invocation, which lets you call a method based upon the runtime type referenced by the object variable, e.g.

object o = ...;
SomeMethod((dynamic) o);

private void SomeMethod<T>(IList<T> list) { ... }
private void SomeMethod(string str) { ... }

Though, of the top of my head, I cannot remember if you can do a dynamic invocation to a method with a type parameter. If not, you'd have to replace SomeMethod<T>(IList<T> list) with SomeMethod(IEnumerable e).

Personally I'd take the first path. Whilst cruder it's easier to read and debug.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
1

If you look into the List T definition on MSDN, you can observe that it actually inherits a non-generic IList interface.

So you can use this to check if your object is implementing this interface. Any List will implement IList.

This implies one thing, remove the generic parameter from your ToDisplay method.

Also, you can use the is keyword to check that your object is in the inheritance tree (ref here).

Note, to use it, you must add using System.Collections;

public static class IListExtension
{
  public static string ToDisplay(this IList source, string seperator = ",")
  {
    string display = string.Empty;
    // create the display string from the elements of this list
    return display;
  }
}

public string CreateDisplayString(object element)
{
  if (element == null)
    return string.Empty;

  if (element is Ilist)
    return (element as IList).ToDisplay();

  return element.ToString();
} 
Martin Verjans
  • 4,675
  • 1
  • 21
  • 48
1

Do you have some limitations for T? If not you could use Non-Generic IList:

public static string ToDisplay(this IList source, string seperator = ",")
{
    StringBuilder str = new StringBuilder();
    foreach (var o in source)
    {
        if (o != null)
        {
            if (str.Length > 0)
                str.Append(seperator);

            IList list = o as IList;
            if (list != null)
                str.Append(list.ToDisplay(seperator));
            else
                str.Append(o);
        }
    }
    return str.ToString().TrimEnd();
}
Pablo notPicasso
  • 3,031
  • 3
  • 17
  • 22
0

Why not create an overload, using object as parameter might not be the best thing to do.

For List:

public string CreateDisplayString<T>(IList<T> element)
{
  if (element == null)
    return string.Empty;

  return element.ToDisplay();
} 

For String:

public string CreateDisplayString(string element)
{
  if (element == null)
    return string.Empty;

  return element.ToString();
} 
Harsh
  • 3,683
  • 2
  • 25
  • 41
-1

Example below uses reflection to detect if the type is an IList so it will be safe to cast it as a IList.

var lst = new List<SomeObject>();
var obj = lst;

var type = obj.GetType();
var ilistType = typeof(IList<>);


if(type == ilistType || type.GetInterfaces().Any(c=>c.IsGenericType && c.GetGenericTypeDefinition() == ilistType)){
Console.WriteLine("object is a list");
// code here
}
ngeksyo
  • 419
  • 3
  • 10