C# 将多态对象强制转换为特定类型,而不知道该“特定类型”是什么

C# cast polymorphic object into specific type without knowing what that 'specific type' is

提问人:Argo 提问时间:12/29/2022 最后编辑:Argo 更新时间:1/12/2023 访问量:354

问:

谢谢,社区!

正如标题所描述的,我想将父类型的对象转换为子类型,子类型实际上是子类型,而这种“特定类型”在运行时之前无法知道。

假设我有以下数据持有者:

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>();
    }
}

我不知道如何处理这个问题,也没有搜索关键字来捕捉有关这个问题的提示。 在发布之前出现的自动建议似乎不是我的情况,它们是:

C# 在运行时创建(或强制转换)特定类型的对象

C# 在不知道特定类型的情况下访问泛型方法


编辑

多亏了 @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 ObjectSystem.Reflection 都是我一直在寻找的简单且合适的解决方案,但它们总体上并不是最好的解决方案

一开始,我误解了@OlivierJacot-Descombes的答案。他总体上指出了两个障碍:首先,我的类结构正在破坏多态性,其次,反射速度很慢(后来我注意到,动态对象也是如此)。一开始我没有抓住最后一点,所以我走了很长一段路。

此外,事实证明,我无法将动态对象用于我的项目上下文,因为我使用的不是普通的 C#,而是 Unity C#。从技术上讲,我可以,但它们不能很好地融合。

值得庆幸的是,我修改后的解决方案是可以接受的。因此,我决定选择@OlivierJacot-Descombes的帖子作为答案。但我希望,尽管如此,人们还是会接近我,给我留下一个好的建议。

谢谢大家。

C# 泛型强制 转换 多态性

评论

0赞 Valeriy Vartumyan 12/29/2022
您如何看待显式/隐式运算符的使用?
3赞 Guru Stron 12/29/2022
您能否举例说明此层次结构的用法?以及代表您正在经历/试图解决的实际问题的代码?
0赞 Valeriy Vartumyan 12/29/2022
我认为这是一个好主意,但不是,我们不能在泛型中使用显式运算符。stackoverflow.com/questions/2941509/......
1赞 W.F. 12/29/2022
我没有看到在运行时进行投射的好处,因为投射是编译时的东西,也许您正在寻找动态对象?
1赞 W.F. 12/30/2022
你想要的可能是这样的:dotnetfiddle.net/x95V0J(在示例中,当对象类型在运行时已知时,没有显式强制转换的意义,但我们接受它)

答:

0赞 Valeriy Vartumyan 12/29/2022 #1

社区!这是个好问题。这很有趣。 我认为这是这个问题的简单解决方案。 我们只需要创建一个简单的构造函数,如下所示


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);
    }
    
}

评论

0赞 Argo 12/30/2022
谢谢你的回答,但我有点迷茫。你能提供一些额外的解释吗?关于你的答案,我有 2 个问题:1) 字符串 SomeData 有什么用?目前,T 值和字符串 SomeData 之间没有生动的相关性。由于我将始终使用 T 值,因此字符串 SomeData 似乎只占用内存空间。2) 该构造函数的目的是什么?看起来,它所做的纯粹是获取另一个持有者的 SomeData 并将其设置为自己的 SomeData,而 T Value 则与 SomeData 完全无关。
1赞 Valeriy Vartumyan 12/31/2022
1) 字段 SomeData,注释仅用于显示如何不丢失保存在 Holder<T 中的 Holder 中的数据> 2) 复制构造函数需要在 Holder<T> 类中使用它,以便轻松地从基类创建泛型
1赞 Olivier Jacot-Descombes 12/29/2022 #2

如果需要强制转换为特定类型,则多态性是错误的。当然,你可以做这样的事情:

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();

...无需投射。


更新

您编辑的问题确实显示了多态版本。

在这里,我想介绍另一种方法,通过使用接口将 和 合并到一个类中。HolderHolder<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 对象检索不同类型的值。ValueIHolderobjectpublic object Valueobject IHolder.ValueList<IHolder>Holder<T>list[i].Value

但是你有一个变量,你可以得到强类型值。Holder<float> floatHolderfloat

请注意,这仍然允许您派生更具体的类型,例如 ,但它甚至可能不是必需的。class FloatHolder : Holder<float>

如果打算只使用派生类型,则可以标记为抽象类型,也可以将派生类必须实现的所有成员标记为抽象类型。这使得无法创建 with 的实例,并且还允许您声明没有 body 的抽象方法。Holder<T>Holder<T>new

评论

0赞 Argo 12/30/2022
首先,谢谢你。其次,我是故意这样做的,所以除非我找到更合适的解决方案,否则我不能偏离这条路。我知道的一个事实是,运行时中存在的每个 Holder 实例都将是 Holder<T> 类型。另一件事是,我目前没有计划创建继承自 Holder<T> 的类。正在创建 Holder<T> 的实例,如我的第二个代码块所示。但是,如果我做了一个,这可能是我的解决方案,因为没有这样的 T.ToString() 是我发布这个问题的原因。简单的公共虚字符串 GetValue() 就可以了。
0赞 Olivier Jacot-Descombes 12/30/2022
没有,因为是一种类型。但是,当 Holder<T>' 并且泛型类型参数未知时,也有一个 和 偶数。后者必须被覆盖才能使某些东西有用。还可以获取类型名称,其中 或 where 返回对象。T.ToString()Tholder.Value.ToString()holder.ToString()holder is a ToStringtypeof(T).Nametypeof(T).ToString()typeofSystem.Type
1赞 Olivier Jacot-Descombes 12/30/2022
这是一个典型的 XY 问题,因为您询问的是您尝试的解决方案,而不是您的实际问题。
0赞 Argo 12/30/2022
也许是的,这是一个 XY 问题。但请理解,因为那一定是我造成的,在我写这篇文章之前不知道从哪里开始。非常感谢评论中的@W.F.,使用关键字动态对象,我可以开始有效地搜索。在那里,我可以找到一个System.Reflection,这似乎是我一直在寻找的最合适的解决方案。 感觉像是我想要的结果,但这仍然打破了多态性。在此基础上,您能否给我一个更好的演练方法,以便我可以在不破坏多态性的情况下获得相同的结果?holder.GetType().GetProperty("GetValue").Invoke(holder, null);
0赞 Olivier Jacot-Descombes 12/30/2022
像这样在运行时获取强类型值是没有意义的,因为由于您不知道它的类型,因此从现在开始,您将被迫通过 Reflection 执行所有操作。这是尝试使用泛型做一些动态操作的典型案例。但通用绝不是动态的。它们总是在编译时解析。给定一个 Holder<T> Holder,您可以在不知道其类型的情况下进行书写。object value = holder.Value;