提问人:Argo 提问时间:12/29/2022 最后编辑:Argo 更新时间:1/12/2023 访问量:354
C# 将多态对象强制转换为特定类型,而不知道该“特定类型”是什么
C# cast polymorphic object into specific type without knowing what that 'specific type' is
问:
谢谢,社区!
正如标题所描述的,我想将父类型的对象转换为子类型,子类型实际上是子类型,而这种“特定类型”在运行时之前无法知道。
假设我有以下数据持有者:
public class Holder {}
public class Holder<T> : Holder
{
public T Value;
}
这个 Holder(不是 Holder<T>)将在运行时被赋予某些脚本。
我需要将此 Holder 转换为 Holder<T>(例如,Holder<string>),以便我可以访问 Value : T。
现在,我只能每年添加铸造案例及其对应方法来处理它,但随着时间的推移,这个 Holder<T> 会有更多的类型,以这种方式进行管理将变得不可能。
有没有办法实现这个目标?
此 Holder 不得扁平化,因为它在以下上下文中使用:
public class SomeNode
{
protected Holder holder;
}
public class SomeNode<T> : SomeNode
{
public SomeNode<T>()
{
holder = new Holder<T>();
}
}
我不知道如何处理这个问题,也没有搜索关键字来捕捉有关这个问题的提示。 在发布之前出现的自动建议似乎不是我的情况,它们是:
编辑
多亏了 @W.F.,我可以使用一个有效的关键字“动态对象”开始搜索,最终我发现 System.Reflection 是我想要的灵魂。
它看起来像下面,目前它解决了我的直接问题:
holder.GetType().GetProperty("GetValue").Invoke(holder, null);
但正如@OlivierJacot-Descombes所指出的,我的结构和使用它的方式正在打破多态性的目的。因此,我仍然需要一个更好的解决方案,它可以完成我正在寻找的工作,并且不会破坏多态性。
我脑海中可能出现的解决方法是,首先,在 Holder 中创建一个 GetValue() 方法,并创建继承自 Holder 的类来实现此方法:
public class Holder
{
public virtual string GetValue() => "";
}
public class Holder<T> : Holder
{
public T Value;
}
public class FloatHolder : Holder<float> //for example
{
public override string GetValue() => Value.ToString();
}
其次,更改节点结构,例如:
public class SomeNode
{
protected Holder holder;
}
public class SomeNode<T> : SomeNode {}
public class FloatNode : SomeNode<float>
{
public FloatNode()
{
holder = new FloatHolder();
}
}
然后,我可以这样做:
public class EchoNode : SomeNode
{
public void Tick()
{
Console.WriteLine(holder.GetValue());
}
}
似乎创建了太多的类,但似乎也没有破坏多态性。
寻求进一步的建议。再次感谢!
编辑#2
我已经在评论中说过了,但为了更好的可读性,我也在这里写了这个。
Dynamic Object 和 System.Reflection 都是我一直在寻找的简单且合适的解决方案,但它们总体上并不是最好的解决方案。
一开始,我误解了@OlivierJacot-Descombes的答案。他总体上指出了两个障碍:首先,我的类结构正在破坏多态性,其次,反射速度很慢(后来我注意到,动态对象也是如此)。一开始我没有抓住最后一点,所以我走了很长一段路。
此外,事实证明,我无法将动态对象用于我的项目上下文,因为我使用的不是普通的 C#,而是 Unity C#。从技术上讲,我可以,但它们不能很好地融合。
值得庆幸的是,我修改后的解决方案是可以接受的。因此,我决定选择@OlivierJacot-Descombes的帖子作为答案。但我希望,尽管如此,人们还是会接近我,给我留下一个好的建议。
谢谢大家。
答:
社区!这是个好问题。这很有趣。 我认为这是这个问题的简单解决方案。 我们只需要创建一个简单的构造函数,如下所示
public class Holder
{
public string SomeData; // just example data
public Holder()
{
}
public Holder(Holder someData)
{
SomeData = someData.SomeData;
}
}
public class Holder<T> : Holder
{
public T Value;
public Holder(Holder a, T t = default)
:base(a)
{
Value = t;
}
}
public class Programm
{
void Main()
{
var h = new Holder();
var g = new Holder<string>(h);
}
}
评论
如果需要强制转换为特定类型,则多态性是错误的。当然,你可以做这样的事情:
switch (holder)
{
case Holder<string> stringHolder:
DoStringThing(stringHolder.Value);
break;
case Holder<int> intHolder:
DoIntThing(intHolder.Value);
break;
...
}
另请参阅:使用模式切换语句。
然而,多态性背后的想法是能够在不必知道特定类型的情况下做事。因此,重新设计持有者类,并让它们自己执行特定于类型的事情:
public abstract class Holder
{
public abstract void DoThing();
}
public abstract class Holder<T> : Holder
{
public abstract T Value { get; }
}
特定类型的一些示例:
public class StringHolder : Holder<string>
{
public StringHolder(string value)
{
Value = value;
}
public override string Value { get; }
public override void DoThing()
{
Console.WriteLine($"String of length {Value.Length} is \"{Value}\"");
}
}
public class IntHolder : Holder<int>
{
public IntHolder(int value)
{
Value = value;
}
public override int Value { get; }
public override void DoThing()
{
Console.WriteLine($"The integer {Value} is {(Value % 2 == 0 ? "even" : "odd")}");
}
}
现在你可以简单地写
holder.DoThing();
...无需投射。
更新
您编辑的问题确实显示了多态版本。
在这里,我想介绍另一种方法,通过使用接口将 和 合并到一个类中。Holder
Holder<T>
public interface IHolder
{
object Value { get; set; }
}
public interface IHolder<T> : IHolder
{
new T Value { get; set; } // The new keyword hides the inherited property.
}
public class Holder<T> : IHolder<T>
{
object IHolder.Value
{
get => Value; // Returns T Holder<T>.Value as object.
set => Value = value is T t ? t : default; // Sets T Holder<T>.Value.
}
public T Value { get; set; }
}
Holder<T>
现在实现基于类型声明的“中性”属性。由于它显式实现它(即,而不是我们编写),因此此属性是隐藏的,除非通过接口访问它。例如,这允许您声明 a 并使用 as 对象检索不同类型的值。Value
IHolder
object
public object Value
object IHolder.Value
List<IHolder>
Holder<T>
list[i].Value
但是你有一个变量,你可以得到强类型值。Holder<float> floatHolder
float
请注意,这仍然允许您派生更具体的类型,例如 ,但它甚至可能不是必需的。class FloatHolder : Holder<float>
如果打算只使用派生类型,则可以标记为抽象类型,也可以将派生类必须实现的所有成员标记为抽象类型。这使得无法创建 with 的实例,并且还允许您声明没有 body 的抽象方法。Holder<T>
Holder<T>
new
评论
T.ToString()
T
holder.Value.ToString()
holder.ToString()
holder is a
ToString
typeof(T).Name
typeof(T).ToString()
typeof
System.Type
holder.GetType().GetProperty("GetValue").Invoke(holder, null);
object value = holder.Value;
评论