为扩展方法问题转换IEnumerable <t> </t>

时间:2010-07-09 01:47:40

标签: c# generics covariance

我有以下类和扩展类(对于此示例):

public class Person<T>
{
    public T Value { get; set; }
}

public static class PersonExt
{
    public static void Process<TResult>(this Person<IEnumerable<TResult>> p)
    {
        // Do something with .Any().
        Console.WriteLine(p.Value.Any());
    }
}

我原本以为我可以写下面的内容,但它会起作用,但它不会:

var x = new Person<List<String>>();
x.Process();

由于List在继承树中比IEnumerable低,所以这不应该有效吗?如果我新建一个Person<IEnumerable<String>>当然是因为那是直接类型。

我正在尝试使用可以应用于所有Person<T>的扩展方法,只要T实现IEnumerable<Something>,因为我需要使用.Any()方法。

编辑:也许我对协方差的理解是关闭的?我知道IEnumerable<String>应转换为IEnumerable<Object>,但无法IList<String>转换为IEnumerable<String>

EDIT2:忘了提及我使用.net 4.0。

3 个答案:

答案 0 :(得分:2)

  

我知道IEnumerable<String>应该   转换为IEnumerable<Object>,但是   无法IList<String>转换为   IEnumerable<String>

IList<String>可以转换为IEnumerable<String>。问题是您尝试将Person<List<String>>转换为Person<IEnumerable<String>>,这是非法的。例如,写下来是完全有效的:

var x = new Person<IEnumerable<String>>();
x.Value = new string[0];

因为Value的类型为IEnumerable<String>,而字符串数组为IEnumerable<String>。但是,你不能写:

var x = new Person<List<String>>();
x.Value = new string[0];

因为值类型为List<String>。由于您无法在可以使用Person<List<String>>的所有地方使用Person<IEnumerable<String>>,因此它不是合法演员。

请注意,如果向扩展方法添加第二个类型参数,则可以执行类似于所需的操作:

public static void Process<TResult, TList>(this Person<TList> p)
    where TList : IEnumerable<TResult>
{
    Console.WriteLine(p.Value.Any());
}

不幸的是,编译器无法推断出两个类型参数,所以你必须这样调用它:

var x = new Person<List<String>>();
x.Process<String, List<String>>();

如果您使用的是C#4.0且可以使用协方差,则可以为人员定义协变接口

public interface IPerson<out T>
{
    T Value { get; }
}

public class Person<T>
    : IPerson<T>
{
    public T Value { get; set; }
}

然后将您的扩展方法编写为:

public static void Process<TResult>(this IPerson<IEnumerable<TResult>> p)
{
    // Do something with .Any().
    Console.WriteLine(p.Value.Any());
}

由于IPerson<T>.Value是只读的,IPerson<List<String>>可以在IPerson<IEnumerable<String>>可以使用的任何地方使用,并且转换有效。

答案 1 :(得分:1)

我不确定你是否已经掌握了正确使用仿制药的方法。无论如何......

唯一不正确的是您的扩展方法声明,以及您尝试约束扩展方法的方式。

public static class ThingExtensions
{
    public static void Process<T>(this Thing<T> p)
        where T : IEnumerable<string>
    {
        // Do something with .Any().
        Console.WriteLine(p.Value.Any());
    }
}

我真正做的就是将Person重命名为Thing,这样我们就不会对Person<List<string>>真正的内容感到困惑。

public class Thing<T>
{
    public T Value { get; set; }
}

class ListOfString : List<string>
{ }

class Program
{
    static void Main(string[] args)
    {
        var x = new Thing<ListOfString>();
        x.Value = new ListOfString();
        x.Process();

        x.Value.Add("asd");
        x.Process();


        var x2 = new Thing<int>();
        // Error    1   The type 'int' cannot be used as type parameter 'T' 
        // in the generic type or method 
        // 'ThingExtensions.Process<T>(Thing<T>)'. 
        // There is no boxing conversion from 'int' to 
        // 'System.Collections.Generic.IEnumerable<string>'.    
        //x2.Process();

        Console.Read();
    }
}

如果更适用,您也可以将通用约束移动到Thing<T>

答案 2 :(得分:0)

你提到协方差,但实际上并没有使用它。您必须在通用参数上指定inout。请注意,共同/逆转不适用于类类型;它们必须应用于接口。

因此,引入一个接口并使其协变:

public interface IPerson<out T>
{
  T Value { get; }
}

public class Person<T> : IPerson<T>
{
  public T Value { get; set; }
}

public static class PersonExt
{
  public static void Process<TResult>(this IPerson<IEnumerable<TResult>> p)
  {
    // Do something with .Any(). 
    Console.WriteLine(p.Value.Any());
  }
}

允许编译此代码:

var x = new Person<List<String>>();  
x.Process();