Преобразование IEnumerable <T>для выпуска метода расширения - PullRequest
4 голосов
/ 09 июля 2010

У меня есть следующий класс и класс расширения (для этого примера):

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: забыл упомянуть, что я am , используя .net 4.0.

Ответы [ 3 ]

2 голосов
/ 09 июля 2010

Я знаю, 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];

, поскольку значение имеет тип 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 голос
/ 09 июля 2010

Я не уверен, что вы правильно поняли использование дженериков.В любом случае ...

Единственное, что неверно, это ваше объявление метода расширения и способ, которым вы пытаетесь ограничить метод расширения.

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>если бы это было более применимо.

0 голосов
/ 09 июля 2010

Вы упоминаете ковариацию, но на самом деле не используете ее.Вы должны указать in или out в общих параметрах.Обратите внимание, что co / contravariance не работает на типах классов;они должны применяться к интерфейсам.

Итак, введение интерфейса и его ковариантность:

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