【问题标题】:Why does dynamic type work where casting does not?为什么动态类型在强制转换不起作用的情况下起作用?
【发布时间】:2010-10-29 07:50:23
【问题描述】:

到目前为止,我的猜测是动态类型只是在编译期间“关闭”类型检查,并且在动态实例上调用消息时执行类似于类型转换的操作。显然还有其他事情发生。

附加的 NUnit 测试用例显示了我的问题:使用动态类型我可以使用仅在具体子类中可用的方法,但我不能使用强制转换来做同样的事情(导致 InvalidCastException)。我宁愿进行强制转换,因为这可以让我在 VS 中完成完整的代码。

谁能解释正在发生的事情和/或提示我如何在我的案例中完成代码,而不必在每个具体子类中重新实现 WorkWithAndCreate 方法?

干杯,约翰内斯

using System;
using NUnit.Framework;

namespace SlidesCode.TestDataBuilder
{
    [TestFixture]
    public class MyTest
    {
        [Test]
        public void DynamicWorks()
        {
            string aString = CreateDynamic(obj => obj.OnlyInConcreteClass());
            Assert.AreEqual("a string", aString);
        }

        private static string CreateDynamic(Action<dynamic> action)
        {
            return new MyConcreteClass().WorkWithAndCreate(action);
        }

        [Test]
        public void CastingDoesNotWorkButThrowsInvalidCastException()
        {
            string aString = CreateWithCast(obj => obj.OnlyInConcreteClass());
            Assert.AreEqual("a string", aString);
        }

        private static string CreateWithCast(Action<MyConcreteClass> action)
        {
            return new MyConcreteClass().WorkWithAndCreate((Action<MyGenericClass<string>>) action);
        }
    }

    internal abstract class MyGenericClass<T>
    {
        public abstract T Create();
        public T WorkWithAndCreate(Action<MyGenericClass<T>> action)
        {
            action(this);
            return this.Create();
        }
    }

    internal class MyConcreteClass : MyGenericClass<string>
    {
        public override string Create()
        {
            return "a string";
        }

        public void OnlyInConcreteClass()
        {
        }
    }
}

这是我评论中格式化的真实世界示例:

Customer customer = ACustomer(cust =>
        {
            cust.With(new Id(54321));
            cust.With(AnAddress(addr => addr.WithZipCode(22222)));
        });

private static Address AnAddress(Action<AddressBuilder> buildingAction)
{
    return new AddressBuilder().BuildFrom(buildingAction);
}

private static Customer ACustomer(Action<CustomerBuilder> buildingAction)
{
    return new CustomerBuilder().BuildFrom(buildingAction);
}

其中缺少一些细节,但我希望它能说明目的。

【问题讨论】:

  • 请提供完整的错误信息
  • 我认为为您的实际示例制定设计注意事项可能值得提出另一个问题。必须小心你如何表达它(所以它不是开放式的),但我希望有一个评论空间以外的地方来写我的回复:)

标签: c# dynamic casting generics


【解决方案1】:

dynamic 起作用的原因是dynamic 不依赖于对象类型的编译时知识。 MyGenericClass&lt;string&gt; 没有方法OnlyInConcreteClass(),但是你传递的实例当然有方法,dynamic 找到了这个。

顺便说一句,你可以让WorkWithAndCreate 像这样工作:

public T WorkWithAndCreate<T1>(Action<T1> action)
    where T1 : MyGenericClass<T>
{
    action((T1)this);
    return this.Create();
}

然后,调用也将起作用:

private static string CreateWithCast(Action<MyConcreteClass> action)
{
    return new MyConcreteClass().WorkWithAndCreate(action);
}

您现在不必再投射了。

关于您的构建器,以下操作是否可行?

private static TResult AnInstance<TBuilder, TResult>(Action<TBuilder> buildingAction)
    where TBuilder : Builder<TResult>, new()
{
    return new TBuilder().BuildFrom(buildingAction);
}

【讨论】:

  • +1。获得代码完成的唯一方法是拥有一个具体类型,而使您的具体类型可以在派生类型中更改的唯一方法是将其编写为具有基本类型约束的泛型函数。
  • 这解决了手头的问题-非常感谢。我仍然愿意就如何改进构建器语法提出建议,但这可能值得提出一个新问题。
【解决方案2】:

这是一个如何使用动态的例子:

http://msdn.microsoft.com/en-us/library/dd264736.aspx

你说:

到目前为止,我的猜测是动态类型只是在编译期间“关闭”类型检查,并且在动态实例上调用消息时执行类似于类型转换的操作

实际上,它使用反射在运行时查找您按名称调用的方法、属性和字段。除非您将对象实际转换回其基础类型,否则不会进行转换。

至于你的实际问题,你能举一个更具体的例子吗?可能有更好的设计,但您没有告诉我们您想要做什么 - 只是您目前正在做什么。

冒昧地猜测一下,您可能想要使用基础interface,并让您的所有函数都接受该基础接口。然后将您要调用的方法放在该接口上,并在您的具体类型中实现它们。通常dynamic 在您没有基类型或无法修改基类型以添加​​虚拟或抽象方法时用作解决方法。

如果你真的想让它按原样工作,你必须用泛型类型参数而不是动态类型参数来编写它。请参阅 Pieter 的解决方案,了解如何正确执行此操作。

【讨论】:

  • 我想要做的是想出一个易于在测试用例中构建测试数据的语法。标准模式(来自 Java 社区)是在构建器对象上有一个流畅的接口,并调用 Build() 作为实际创建对象的最后一步。使用 lambda 代替了 Build() 但引入了其他挑战 - 特别是如果我想创建一个通用的方法来做到这一点。
  • @johanneslink:您能否提供一些您想要启用的示例语法,以及几个不同的场景?以这种方式向后工作更容易。 “构建测试数据”可以表示任何数量的东西。过早地让您的要求过于笼统可能会带来一些麻烦,在这里。
  • @johanneslink:我发现复制模式和代码几次更容易,然后在我有一些具体要求之后尝试将这些概念连接到一个通用概念。制作一个良好的流畅界面(基本上是一种特定于领域的语言)已经足够复杂了。
  • 这是一个真实的例子:customer = ACustomer(cust => { cust.With(new Id(54321)); cust.With(AnAddress(addr => addr.WithZipCode(22222))) ; });私有地址 AnAddress(Action buildingAction) { return new AddressBuilder().BuildFrom(buildingAction); } private Customer ACustomer(Action buildingAction) { return new CustomerBuilder().BuildFrom(buildingAction); } 使 BuildFrom 泛型解决了代码完成问题
  • @johanneslink:你可以在这里做几件事来简化这段代码。一种是简单地使用初始化语法:var customer = new Customer() { Id = new Id(54321), Address = new Address() { ZipCode = 22222 }, };。如果这不可用,您可以像 Rhino.Mocks 那样做一些事情,并创建通用扩展方法:public static T With&lt;T&gt;(this T instance, Action&lt;T&gt; mutator) { mutator(instance); return instance; },然后像这样使用它:var customer = new Customer().With(AnId(12345))。您的 AnId 函数将返回一个委托 - return c =&gt; c.Id = new Id(value)
猜你喜欢
  • 1970-01-01
  • 2021-09-29
  • 2022-01-21
  • 2019-12-04
  • 2023-03-17
  • 1970-01-01
  • 2014-06-03
  • 2020-05-11
  • 1970-01-01
相关资源
最近更新 更多