编辑:这不是解决此问题的最佳方法。我设法更改设计并使用列表/字典,该列表/字典根据所选键创建类的新实例。这样,您就不需要任何强制转换,反射或切换语句。
我有一个问题要尝试自己解决,但我对自己的解决方案不满意。让我们举个例子:我有一个Farm类,一个Mill类和一个Bakery类。这些是建筑物,我想将它们存储在列表中,很明显,我想在需要时管理其元素。在这种情况下,这是服务器端代码,该列表将需要包含玩家所有建筑物的列表,以便我可以在他要求时(例如加入时)将该列表发送给他。玩家可以建立一个农场开始种植一些小麦,然后建立一个磨坊开始碾碎以制造面粉。然后,他建立了一个面包店来做一些面包或馅饼。这是示例代码:
public class Program
{
public List<> Buildings = new List<>() //Or whatever solution to store them
}
public class Farm
{
public string Name;
public int CropType;
public float Timer;
}
public class Mill
{
public string Name;
public float Timer;
}
public class Bakery
{
public string Name;
public int ProductionType;
public bool IsOpen;
public float Timer;
}
我尝试使用接口,抽象类和普通类从中派生,但是我没有找到解决方案。
这是我的尝试:
public interface IBuilding
{
string ExposedName { get; set; }
string ExposedDescription { get; set; }
}
public class Farm : IBuilding
{
public string Name { get { return Name; } set { Name = value; } }
public string Description { get { return Description; } set { Description = value; } }
public int Crop;
}
public class Mill : IBuilding
{
public string Name { get { return Name; } set { Name = value; } }
public string Description { get { return Description; } set { Description = value; } }
public float Timer;
}
我希望能够在不使用性能不佳的代码的情况下访问List的每个元素的每个变量或您建议的任何解决方案。
答案 0 :(得分:2)
根据您的尝试,但带有一个抽象类:
public abstract class Building
{
string Name { get; set; }
string Description { get; set; }
}
public class Farm : Building
{
public int Crop { get; set; }
}
public class Mill : Building
{
public float Timer { get; set; }
}
解决方案的另一部分可能是IEnumerable可能性(如OfType),可以循环访问列表中各项的属性。 (没有“手动”强制转换,也没有多余的“ isXXX”代码)
class MyCollection
{
List<Building> buildings;
public MyCollection()
{
buildings = new List<Building>();
buildings.Add(new Farm() { Crop = 4 });
buildings.Add(new Mill() { Timer = 4.5f });
buildings.Add(new Farm() { Crop = 5 });
buildings.Add(new Farm() { Crop = 6 });
buildings.Add(new Mill() { Timer = 42 });
buildings.Add(new Farm() { Crop = 55 });
}
public void Print()
{
foreach (Farm f in buildings.OfType<Farm>())
{
Console.WriteLine(f.Crop);
}
foreach (Mill m in buildings.OfType<Mill>())
{
Console.WriteLine(m.Timer);
}
}
}
我不知道这是不是你的意思
“我希望能够访问该元素的每个元素的每个变量 列出您不建议使用任何性能的解决方案 代码。”
答案 1 :(得分:2)
在此描述中:
玩家可以建立一个农场开始种植一些小麦,然后建立一个磨坊开始碾碎以制造面粉。然后,他建立了一个面包店来做一些面包或馅饼。
...您描述了三种不同类的三种不同用途。
为了在列表中存储Farm
,Mill
和Bakery
,它们将需要具有某种公共类型。但这确实不重要。它们是三种不同的类,它们执行不同的操作,因此,如果您使用它们进行农作,碾磨和烘烤,那么将它们彼此放在列表中毫无益处。
接口或公共类仅用于描述对象共有或做什么并在该上下文中使用它们。
如果您向客户发送Building
的列表,则客户仅知道他们具有描述和名称。他们无法知道不同的Building
类型的独特行为。他们不应该知道。如果Building
的方法类似:
someBuilding.CloseDoors()
然后,有可能在建筑物上调用该方法而无需知道它是不是面包店,磨坊等。我们不在乎-我们只想关门。但是,如果我们想Bake
,那么我们就不需要Building
。我们需要一个Bakery
。
如果要为客户端对象提供明显不同的功能,则无法列出一种类型的功能。
如何提供此功能的具体细节取决于您的应用程序。但是这里有一些替代方法:
Name
和Description
属性可能使以可重复使用的方式显示建筑物列表更加容易。)选择其中一个并使用它。Bake
API方法,即可在后台管理可用的面包店。答案 2 :(得分:0)
从基类继承是正确的方法。但是,当从列表访问元素时,应仅使用基类的接口。这意味着仅Building
的成员被允许使用。添加新建筑物时,任何依赖于运行时类型检查的操作都会对您不利,因为您必须记住在switch中添加新案例的每个位置。
仅将可在任何建筑物上调用的成员提取为基类,并在派生类中实现它们。
您提到您不仅希望将它们存储在列表中,而且还希望发送给客户端(并可能接收)。因此,我建议像这样定义Building
:
public abstract class Building : ISerializable, IDeserializable {
// here you define interface, members which every building has
public abstract string Name { get; }
public abstract short Id { get; }
public abstract void Update (); // do staff that this type of building does
public abstract void ReadMembers (BinaryReader reader);
public void Write (BinaryWriter writer) {
writer.Write (Id);
WriteMembers (writer);
}
public abstract void WriteMembers (BinaryWriter writer);
}
因此,当您遍历列表时,可以从基类中调用方法:
foreach (var b in buildings) b.Update ();
foreach (var b in buildings) b.Write (writer);
读书很棘手。它需要为您的读者编写一个扩展方法,该方法应该了解ID以创建正确的建筑物类型。
public static class BinaryReaderExtension {
private static Dictionary <short, Func <Building>> generators = new Dictionary <short, Func <Building>> ();
static BinaryReaderExtension () {
generators [0] = () => new Farm ();
generators [1] = () => new Mill ();
}
public static Building ReadBuilding (this BinaryReader reader) {
var b = generators [reader.ReadInt16 ()] ();
b.ReadMembers (reader);
return b;
}
}
重要提示:Id
必须为每个建筑物返回唯一编号,与分机号相同。
实验的空间很大。
您可以使用数组而不是字典。您可以为writer编写扩展名,并从Id
类中删除Write
和Building
。您可以在Building
返回Building
而不是读者扩展名的情况下使用静态方法。您也可以在Building
类中这样做:
private Dictionary <Type, short> codeByType;
private Dictionary <short, Func <Building>> instanceByCode;
private void RegisterBuilding (Type t, short code) {
// custom error checking for code to be unique, etc
}
并通过此函数从Building
静态初始化程序中填充字典。
派生类应定义为:
public class Farm : Building {
public override string Name => "Farm"; // assuming c# 6.0
public override short Id => 0;
private Crop crop;
// other farm-specific things
public override void Update {
// you access a private field,
// but Update method itself can be called even if you don't know the type
// every building has its own implementation
crop.Grow ();
// other farm-specific things
}
// override other methods, feel free to access private members of Farm here
}
答案 3 :(得分:-2)
就创建列表而言,抽象类可能是最佳选择。
由于您要访问每个变量,因此,只要您想访问除基类以外的其他任何内容,就必须将其强制转换。没有强制转换就无法解决您想要的确切行为(据我所知)。
如果确实需要,请仅使用以下内容。这很丑,我只是提出来,因为我曾经真的找不到解决方法。
当遇到这样的问题时,我将在抽象类(Building)中创建一个枚举类型的“ Type”属性。在所有实现(Mill,Bakery)的构造函数中,将此属性设置为硬编码的枚举值。
然后,您可以(通过开关)检查它是哪座建筑物,并进行相应的投射/使用。
编辑:OP说不是这种情况。
如果建筑物可以同时包含多个事物(例如面包店和磨坊),则应使用“ isBakery”和“ isMill”,而不要使用单个枚举属性。
另请参阅我的评论。我总是会尽量避免处于必须使用这种模式的位置。
答案 4 :(得分:-2)
这就是我要设置的方式。就像其他人提到的那样,您需要从建筑继承。在循环中运行时,只需检查建筑物是农场还是面包店即可。
public abstract class Building
{
public string Name { get; set; }
public float Timer { get; set; }
}
public class Farm : Building
{
public int CropType { get; set; }
public Farm()
{
}
public Farm(string name, int cropType, float timer)
{
Name = name;
Timer = timer;
CropType = cropType;
}
}
public class Bakery : Building
{
public int ProductionType { get; set; }
public bool IsOpen { get; set; }
public Bakery()
{
}
public Bakery(string name, float timer, int prodType, bool isOpen)
{
Name = name;
Timer = timer;
ProductionType = prodType;
IsOpen = isOpen;
}
}
public class Example
{
public static void myMethod()
{
Bakery bakery1 = new Bakery("Bake1", 123, 1, true);
Farm farm1 = new Farm("Farm1", 2, 321);
List<Building> buildings = new List<Building>();
buildings.Add(bakery1);
buildings.Add(farm1);
foreach(Building building in buildings)
{
if(building is Farm)
{
Farm f1 = (Farm)building;
int cropType = f1.CropType;
}
else if(building is Bakery)
{
Bakery b1 = (Bakery)building;
int prodType = b1.ProductionType;
}
}
}
}