接口与委托中的变体(in/out)
理解 C# 变体:接口和委托中的 out 用于生产者(协变), in 用于消费者(逆变)。
接口与委托中的变体(in/out) 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 3 节课。
方差概览
方差让泛型接口和委托能够与继承配合使用。
- 输出 = 协变(生产者)
- 输入 = 逆变(使用者)
- 当接口只生成或只使用 T 时,这种用法是安全的
协变演示
IEnumerable<out T> 是协变的:在需要动物序列的地方,可以使用猫序列。
using System;
using System.Collections.Generic;
public class Animal { public virtual string Name() { return "animal"; } }
public class Cat : Animal { public override string Name() { return "cat"; } }
public class Program
{
static void PrintAll(IEnumerable<Animal> animals)
{
foreach (Animal a in animals)
{
Console.WriteLine(a.Name());
}
}
public static void Main(string[] args)
{
List<Cat> cats = new List<Cat>();
cats.Add(new Cat());
// IEnumerable<Cat> -> IEnumerable<Animal> is allowed (out T)
PrintAll(cats);
}
}
逆变演示
IComparer<in T> 是逆变的:Animal 的比较器同样适用于 Cat,因为它只消费 T。
using System;
using System.Collections.Generic;
public class Animal { public string N; public Animal(string n){ N=n; } }
public class Cat : Animal { public Cat(string n) : base(n) {} }
// Comparer can compare any Animals
public class AnimalNameComparer : IComparer<Animal>
{
public int Compare(Animal x, Animal y)
{
return string.Compare(x == null ? "" : x.N, y == null ? "" : y.N, StringComparison.Ordinal);
}
}
public class Program
{
public static void Main(string[] args)
{
List<Cat> cats = new List<Cat>();
cats.Add(new Cat("Miso"));
cats.Add(new Cat("Ada"));
// IComparer<Animal> can be used as IComparer<Cat> (in T)
cats.Sort(new AnimalNameComparer());
foreach (Cat c in cats) Console.WriteLine(c.N);
}
}
委托变性
委托同样支持变性:Func<out TResult>(生产者)和 Action<in T>(消费者)。
using System;
public class Animal { public virtual string Speak(){ return "???"; } }
public class Cat : Animal { public override string Speak(){ return "meow"; } }
public class Program
{
static Animal MakeAnimal() { return new Animal(); }
static void HandleAnimal(Animal a) { Console.WriteLine(a.Speak()); }
public static void Main(string[] args)
{
// Func<Cat> can be used where Func<Animal> is expected? Yes, because TResult is out.
Func<Animal> maker = new Func<Cat>(delegate { return new Cat(); });
Console.WriteLine(maker().Speak());
// Action<Animal> can be used where Action<Cat> is expected? Yes, because T is in.
Action<Cat> catSink = new Action<Animal>(HandleAnimal);
catSink(new Cat());
}
}
自定义变性接口
设计规则:将仅用于生产的参数标记为 out,将仅用于消费的参数标记为 in,从而支持安全转换。
using System;
// Producer-only interface: out T
public interface ISource<out T>
{
T Get();
}
// Consumer-only interface: in T
public interface ISink<in T>
{
void Put(T item);
}
public class Animal { public string N; }
public class Cat : Animal { }
public class CatSource : ISource<Cat>
{
public Cat Get() { return new Cat(); }
}
public class AnimalSink : ISink<Animal>
{
public void Put(Animal a) { Console.WriteLine("Got animal"); }
}
public class Program
{
public static void Main(string[] args)
{
ISource<Animal> animals = new CatSource(); // out T allows upcast
ISink<Cat> cats = new AnimalSink(); // in T allows down-assign
animals.Get();
cats.Put(new Cat());
}
}
变性提示
提示:
- out 参数只能出现在输出位置(返回类型、协变位置)。
- in 参数只能出现在输入位置(方法参数)。
- 声明变性时,不要让同一个 T 同时用于生产和消费。
- 变性适用于接口和委托,不适用于类。
协变关键字
总结
总结:在接口和委托上,对生产者使用 out(协变),对消费者使用 in(逆变)。让 API 保持单向性,从而支持安全转换。
用 AI 导师学习 C# — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 93
- 课程
- 346
常见问题解答
「接口与委托中的变体(in/out)」课时是免费的吗?
是的 — 「接口与委托中的变体(in/out)」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 3 节课。
「接口与委托中的变体(in/out)」这节课中我会学到什么?
理解 C# 变体:接口和委托中的 out 用于生产者(协变), in 用于消费者(逆变)。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 3 节。
「接口与委托中的变体(in/out)」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 泛型方法/类型与约束
- 接口与委托中的变体(in/out)
- 可空引用类型(认知)与注解(C# 6 模式)