提问人:Nick Farsi 提问时间:7/22/2022 最后编辑:Nick Farsi 更新时间:7/22/2022 访问量:73
如何在通用工厂中指向 <T> 的基础类
How to point to underlying class of <T> in generic factory
问:
有一些蔬菜:
public interface IVegetable
{
}
public class Potato : IVegetable
{
}
public class Onion : IVegetable
{
}
我们将重点放在洋葱上并对其进行处理:我有一个通用蔬菜加工机的通用接口和一个洋葱专用接口:
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<Onion>
{
void MakeCry(Onion onion);
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(Onion onion)
{
return "Purple";
}
public void MakeCry(Onion onion)
{
Console.WriteLine($"{onion} made you cry!");
}
}
以及通用工厂:
interface IVegetableProcessorFactory
{
IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable;
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable
{
object processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return (IVegetableProcessor<T>)processor; //this will fail later
}
}
最后,这不起作用:
static void Main(string[] args)
{
var onion = new Onion() as IVegetable;
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}
错误是:
System.InvalidCastException: 'Unable to cast object of type 'OnionProcessor' to type 'IVegetableProcessor`1[Vegetables.Program+IVegetable]'.'
如何让它理解 IVegetable 的基础类并将处理器转换为其对应的类型?
答:
0赞
David L
7/22/2022
#1
事实上,你必须首先进行投射,这是你暗示你的构图中出现了问题。正如 Selvin 在评论中提到的,实现与实现不是一回事。IVegetableProcessor<Onion>
IVegetableProcessor<IVegetable>
您的处理器接口应实现并采用 IVegetable 实例,允许输入参数的逆变:IVegetableProcessor<IVegetable>
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<IVegetable>
{
void MakeCry();
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(IVegetable vegetable)
{
return "Purple";
}
public void MakeCry()
{
Console.WriteLine("You cry now!");
}
}
interface IVegetableProcessorFactory
{
IVegetableProcessor<IVegetable> GetVegetableProcessor(IVegetable vegetable);
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<IVegetable> GetVegetableProcessor(IVegetable vegetable)
{
var processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return processor;
}
}
当通过以下方式运行时,这会正确输出“紫色”:
static void Main(string[] args)
{
var onion = new Onion();
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}
评论
0赞
Nick Farsi
7/22/2022
是的,这有效,但是使用这种方法,OnionProcessor 中的 GetColor 现在将 IVegetable 作为参数而不是 Onion ,这难道不令人困惑吗?如果我们想在这种方法中获得一些洋葱特定的属性怎么办?然后我们必须铸造,你告诉铸造暗示了一个破碎的设计。如果我必须在每种方法中将 IVegetable 转换为 Onion,那么我根本看不到使用泛型的任何优势!
1赞
David L
7/22/2022
#2
你的输入已经是逆变的,但是通过把你的 Onion 实例转换为 ,它不能再在你的工厂中被转换回,因为在这一点上你需要 ,但你拥有的是一个 ,并且你的接口不是协变的。IVegetable
IVetetableProcessor<T>
IVetetableProcessor<Onion>
IVegetableProcessor<IVegetable>
只需删除初始强制转换的 to ,您的代码将按原样工作:new Onion
IVegetable
static void Main(string[] args)
{
var onion = new Onion();
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}
public interface IVegetable { }
public class Potato : IVegetable { }
public class Onion : IVegetable { }
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<Onion>
{
void MakeCry();
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(Onion vegetable)
{
return "Purple";
}
public void MakeCry()
{
Console.WriteLine("You cry now!");
}
}
interface IVegetableProcessorFactory
{
IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable;
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable
{
var processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return (IVegetableProcessor<T>)processor;
}
}
评论
0赞
Nick Farsi
7/22/2022
我特意加入了这个演员阵容,让它看起来像一个真实世界的场景。如果要处理未排序的 IVegetables,我有一个数组是什么?在编译时,我不会知道它们的实际类型。
1赞
David L
7/22/2022
您必须在某个地方承担差异成本。你不能同时存在方差。如果你更喜欢我的其他答案,那也没关系。但是,您必须强制转换实现方法。
1赞
CRice
7/22/2022
这不会在工厂最后一行的运行时崩溃吗?哦,你也换了主,没问题。
0赞
Nick Farsi
7/22/2022
@DavidL换句话说,在这里使用泛型没有真正的好处,因为我们无论如何都必须在某个时候进行强制转换?
0赞
David L
7/22/2022
这实际上取决于您需要泛型执行的操作,这在您的示例中并不清楚。您不能将协方差和逆方差应用于同一方法,这从根本上说是您遇到的问题。因此,如果你想使用泛型,你就必须选择在哪里通过强制转换来表达你的合约的相反行为。
上一个:类型参数必须逆向有效
下一个:Golang 结构体类型转换
评论
IVegetableProcessor<Onion>
不是 的实例,因为没有协方差IVegetableProcessor<IVegetable>
IVegetableProcessor<Onion>
确实继承了 T 最终是 IVegetable 的地方,所以我认为它会起作用......如何做到这一点?IVegetableProcessor<T>
IVegetableProcessor<Onion
>继承自IVegetableProcessor<T>
不... 在哪里......没有继承IVegetableProcessor<Onion>
IVegetableProcessor<T>
T
Onion
interface IVegetableProcessor{} interface IVegetableProcessor<T>:IVegetableProcessor{}
GetVegetableProcessor()
Main()