我有以下类和扩展类(对于此示例):
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。
答案 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)
你提到协方差,但实际上并没有使用它。您必须在通用参数上指定in
或out
。请注意,共同/逆转不适用于类类型;它们必须应用于接口。
因此,引入一个接口并使其协变:
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();