游戏模组开发:如何优化含大量可选参数的C# Item类构造函数?
问题描述
开发游戏模组工具时,需将包含大量可选属性的自定义类型重构为C#的Item类,仅identifier为必填属性,所有类数据均来自XML文件。当前Item类的构造函数因可选参数过多过于冗长,整体结构不够紧凑。考虑过通过字典或自定义类型传递属性,但不确定这两种方案是否更优,希望获取更合适的构造函数及类结构优化方案。
原实现代码:
public class Item( string identifier, string? nameIdentifier = null, string? fallbackNameIdentifier = null, string? descriptionIdentifier = null, string? name = null, string? aliases = null, string? tags = null, Item.Categories? category = null, bool? allowAsExtraCargo = null, float? interactDistance = null, float? interactPriority = null, bool? interactThroughWalls = null, bool? hideConditionBar = null, bool? hideConditionInTooltip = null, bool? requireBodyInsideTrigger = null, bool? requireCursorInsideTrigger = null, bool? requireCampaignInteract = null, bool? focusOnSelected = null, float? offsetOnSelected = null, float? health = null, bool? allowSellWhenBroken = null, bool? indestructible = null, bool? damagedByExplosions = null, float? explosionDamageMultiplier = null, bool? damagedByProjectiles = null, bool? damagedByMeleeWeapons = null, bool? damagedByRepairTools = null, bool? damagedByMonsters = null, bool? fireProof = null, bool? waterProof = null, float? impactTolerance = null, float? onDamagedThreshold = null, float? sonarSize = null, bool? useInHealthInterface = null, bool? disableItemUsageWhenSelected = null, string? cargoContainerIdentifier = null, bool? useContainedSpriteColor = null, bool? useContainedInventoryIconColor = null, float? addedRepairSpeedMultiplier = null, float? addedPickingSpeedMultiplier = null, bool? cannotRepairFail = null, string? equipConfirmationText = null, bool? allowRotatingInEditor = null, bool? showContentsInTooltip = null, bool? canFlipX = null, bool? canFlipY = null, bool? isDangerous = null, int? maxStackSize = null, bool? allowDroppingOnSwap = null, bool? resizeHorizontal = null, bool? resizeVertical = null, string? description = null, string? allowedUpgrades = null, bool? hideInMenus = null, string? subcategory = null, bool? linkable = null, string? spriteColor = null, float? scale = null) { public enum Categories { Decorative, Machine, Medical, Weapon, Diving, Equipment, Fuel, Electrical, Material, Alien, Wrecked, Misc } // All of Item attributes public required string identifier = identifier; public string? nameIdentifier = nameIdentifier; public string? fallbackNameIdentifier = fallbackNameIdentifier; public string? descriptionIdentifier = descriptionIdentifier; public string? name = name; public string? aliases = aliases; public string? tags = tags; public Categories? category = category; public bool? allowAsExtraCargo = allowAsExtraCargo; public float? interactDistance = interactDistance; public float? interactPriority = interactPriority; public bool? interactThroughWalls = interactThroughWalls; public bool? hideConditionBar = hideConditionBar; public bool? hideConditionInTooltip = hideConditionInTooltip; public bool? requireBodyInsideTrigger = requireBodyInsideTrigger; public bool? requireCursorInsideTrigger = requireCursorInsideTrigger; public bool? requireCampaignInteract = requireCampaignInteract; public bool? focusOnSelected = focusOnSelected; public float? offsetOnSelected = offsetOnSelected; public float? health = health; public bool? allowSellWhenBroken = allowSellWhenBroken; public bool? indestructible = indestructible; public bool? damagedByExplosions = damagedByExplosions; public float? explosionDamageMultiplier = explosionDamageMultiplier; public bool? damagedByProjectiles = damagedByProjectiles; public bool? damagedByMeleeWeapons = damagedByMeleeWeapons; public bool? damagedByRepairTools = damagedByRepairTools; public bool? damagedByMonsters = damagedByMonsters; public bool? fireProof = fireProof; public bool? waterProof = waterProof; public float? impactTolerance = impactTolerance; public float? onDamagedThreshold = onDamagedThreshold; public float? sonarSize = sonarSize; public bool? useInHealthInterface = useInHealthInterface; public bool? disableItemUsageWhenSelected = disableItemUsageWhenSelected; public string? cargoContainerIdentifier = cargoContainerIdentifier; public bool? useContainedSpriteColor = useContainedSpriteColor; public bool? useContainedInventoryIconColor = useContainedInventoryIconColor; public float? addedRepairSpeedMultiplier = addedRepairSpeedMultiplier; public float? addedPickingSpeedMultiplier = addedPickingSpeedMultiplier; public bool? cannotRepairFail = cannotRepairFail; public string? equipConfirmationText = equipConfirmationText; public bool? allowRotatingInEditor = allowRotatingInEditor; public bool? showContentsInTooltip = showContentsInTooltip; public bool? canFlipX = canFlipX; public bool? canFlipY = canFlipY; public bool? isDangerous = isDangerous; public int? maxStackSize = maxStackSize; public bool? allowDroppingOnSwap = allowDroppingOnSwap; public bool? resizeHorizontal = resizeHorizontal; public bool? resizeVertical = resizeVertical; public string? description = description; public string? allowedUpgrades = allowedUpgrades; public bool? hideInMenus = hideInMenus; public string? subcategory = subcategory; public bool? linkable = linkable; public string? spriteColor = spriteColor; public float? scale = scale; public List<XmlElement>? children = null; /// <summary> /// Gets this Item represent /// </summary> /// <returns>XmlElement represent of the Item</returns> public XmlElement GetAsXml() { XmlDocument xmlDoc = new(); XmlElement newItem = xmlDoc.CreateElement("Item"); foreach(var attr in this.GetType().GetProperties()) { if (attr.GetValue(this, null) != null) { newItem.SetAttribute(attr.Name.ToLower(), value: attr.GetValue(this, null).ToString()); } } newItem.InnerText = ""; if (this.children != null) { foreach (var child in this.children) { newItem.AppendChild(child); } } return newItem; } }
优化方案
针对你的场景,推荐以下几种更简洁、可维护的方案:
1. 构建器模式(Builder Pattern)
这是处理大量可选参数最常用的方案,可读性和扩展性极强,还能强制保证必填参数不缺失。
实现示例
// 将枚举移出Item类,符合C#编码规范 public enum ItemCategory { Decorative, Machine, Medical, Weapon, Diving, Equipment, Fuel, Electrical, Material, Alien, Wrecked, Misc } public class Item { // 必填属性,用init保证实例创建后不可修改 public required string Identifier { get; init; } // 所有可选属性,统一用自动属性封装 public string? NameIdentifier { get; init; } public string? FallbackNameIdentifier { get; init; } public string? DescriptionIdentifier { get; init; } public string? Name { get; init; } public string? Aliases { get; init; } public string? Tags { get; init; } public ItemCategory? Category { get; init; } public bool? AllowAsExtraCargo { get; init; } public float? InteractDistance { get; init; } // ... 省略其余重复属性 public float? Scale { get; init; } public List<XmlElement>? Children { get; init; } // 私有构造函数,仅允许通过构建器创建实例 private Item(ItemBuilder builder) { Identifier = builder.Identifier; NameIdentifier = builder.NameIdentifier; FallbackNameIdentifier = builder.FallbackNameIdentifier; // ... 为所有属性赋值 Scale = builder.Scale; Children = builder.Children; } // 嵌套构建器类 public class ItemBuilder { // 必填参数在构建器构造函数中强制传入 public string Identifier { get; } // 可选参数默认值为null public string? NameIdentifier { get; set; } public string? FallbackNameIdentifier { get; set; } public string? DescriptionIdentifier { get; set; } public string? Name { get; set; } // ... 省略其余重复属性 public float? Scale { get; set; } public List<XmlElement>? Children { get; set; } public ItemBuilder(string identifier) { Identifier = identifier; } // 链式调用的属性设置方法,每个可选属性对应一个 public ItemBuilder WithNameIdentifier(string? nameIdentifier) { NameIdentifier = nameIdentifier; return this; } public ItemBuilder WithCategory(ItemCategory? category) { Category = category; return this; } // ... 省略其余属性的WithXXX方法 public ItemBuilder WithScale(float? scale) { Scale = scale; return this; } // 最终构建Item实例的方法 public Item Build() { return new Item(this); } } public XmlElement GetAsXml() { XmlDocument xmlDoc = new(); XmlElement newItem = xmlDoc.CreateElement("Item"); foreach(var prop in this.GetType().GetProperties()) { var value = prop.GetValue(this); if (value != null) { newItem.SetAttribute(prop.Name.ToLower(), value.ToString()); } } if (Children != null) { foreach (var child in Children) { newItem.AppendChild(xmlDoc.ImportNode(child, true)); } } return newItem; } }
使用方式
// 仅需设置必填项和需要的可选项,代码可读性极强 var item = new Item.ItemBuilder("medkit_01") .WithName("First Aid Kit") .WithCategory(ItemCategory.Medical) .WithHealth(100f) .WithMaxStackSize(5) .Build();
2. 配置类+精简构造函数
如果觉得构建器模式代码量偏大,可以用单独的配置类封装所有可选属性,构造函数仅接收必填项和可选的配置类参数。
实现示例
public enum ItemCategory { // ... 枚举内容同上 } // 配置类封装所有可选属性 public class ItemOptions { public string? NameIdentifier { get; set; } public string? FallbackNameIdentifier { get; set; } public string? DescriptionIdentifier { get; set; } public string? Name { get; set; } // ... 省略其余重复属性 public float? Scale { get; set; } public List<XmlElement>? Children { get; set; } } public class Item { public required string Identifier { get; init; } public string? NameIdentifier { get; init; } // ... 所有属性定义同上 // 精简构造函数:必填项 + 可选配置类 public Item(string identifier, ItemOptions? options = null) { Identifier = identifier; if (options == null) return; NameIdentifier = options.NameIdentifier; FallbackNameIdentifier = options.FallbackNameIdentifier; // ... 为所有可选属性赋值 Scale = options.Scale; Children = options.Children; } // GetAsXml方法同上 }
使用方式
var options = new ItemOptions { Name = "First Aid Kit", Category = ItemCategory.Medical, Health = 100f, MaxStackSize = 5 }; var item = new Item("medkit_01", options);
3. 额外优化建议
- 避免使用字典方案:字典虽然灵活,但会丢失类型安全,编译时无法检查属性名称和类型,后期维护极易出错,不推荐。
- 优化XML序列化:当前
GetAsXml用反射遍历属性,效率一般。如果XML结构固定,可改用XmlSerializer序列化,或手动映射属性,避免反射的性能开销。 - 属性封装规范:用自动属性替代公开字段,用
init关键字保证实例不可变(如需修改可改用set),提升代码安全性。
内容的提问来源于stack exchange,提问作者Planeptunia
相关产品推荐
相关产品推荐

