将对象转换为 List<T> 以便能够使用扩展方法

Casting an object to List<T> to be able to use extension methods

我已经生成了一个扩展方法来以有意义的方式显示 IList<T>

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;
  }
}

要使用此扩展程序,我可以这样做:

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

我现在的问题是,我有一个获取 object 的方法,它也可以是一个列表。所以,如果对象不是列表,我想使用 ToString() 方法,如果它是列表,我想使用我的扩展。

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();
} 

显然,上面的代码不起作用,因为我无法转换为泛型 IList<T>。如果我使用现有的 class,比如 IList<string>,它可以像我想要的那样用于字符串列表,但当然我不想为每个可能的 IList<[name of class]> 创建一个语句。有没有办法使用扩展方法或任何其他方法从任何类型的列表中获取自定义字符串?

为什么不创建重载,使用对象作为参数可能不是最好的做法。

对于列表:

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

  return element.ToDisplay();
} 

对于字符串:

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

  return element.ToString();
} 

好吧,您基本上是在寻找 运行 时间过载解决方案 - dynamic 就是这样做的。为您想要的单独重载提供单独的方法,它会根据通常的重载解析规则选择正确的方法:

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

然后你可以调用最好的重载(在 运行 时间)只需做

Do((dynamic)someObject);

如果您负担得起 dynamic,这可能是最简单的解决方案。

如果没有,您可以利用类型差异。您可以使用 IEnumerable<T> 而不是 IList<T>(这对于您展示的案例来说似乎已经足够),它与 T 是协变的。这允许你做

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

当然,这只有在 ToDisplay 方法实际上不使用类型参数的情况下才有用。

此外,正如 InBetween 正确指出的那样,值类型不支持方差 - 因此对于 List<int>,这仍然会失败,例如 - (new List<int>() is IEnumerable<object>) == false。它只适用于引用类型。对于这种情况,您仍然可以使用非泛型 IList,但您需要一个单独的 ToDisplay 方法。或者,您可以使用反射来直接使用正确的泛型类型参数调用正确的方法,但到那时您将得到一些与 dynamic 非常相似但更笨重的东西。

你基本上有两个选择:切换到使用 IEnumerable 而不是 IList<T>,因为每个 IList<> 也是一个 IEnumerable。然后,您可以使用 asis 来确定要采用的代码路径,根据需要进行转换以调用适当的方法。

您的另一个选择是使用动态方法调用,它允许您根据 object 变量引用的运行时类型调用方法,例如

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

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

不过,在我的脑海中,我不记得是否可以对带有类型参数的方法进行动态调用。如果不是,则必须将 SomeMethod<T>(IList<T> list) 替换为 SomeMethod(IEnumerable e).

就我个人而言,我会选择第一条路。虽然更粗糙,但更容易阅读和调试。

如果您查看 List T definition on MSDN,您会发现它实际上继承了一个非通用 IList 接口。

所以你可以用它来检查你的对象是否实现了这个接口。任何列表都将实现 IList。

这意味着一件事,从 ToDisplay 方法中删除通用参数。

此外,您可以使用 is 关键字来检查您的对象是否在继承树中 (ref here)。

注意,要使用它,您必须添加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();
} 

下面的示例使用反射来检测类型是否为 IList,因此将其转换为 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
}

你对T有什么限制吗?如果不是,您可以使用非通用 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();
}