【问题标题】:No default constructor error when there is a default constructor有默认构造函数时没有默认构造函数错误
【发布时间】:2023-03-20 09:05:01
【问题描述】:

编辑:将在 SO 上公开生产代码!希望没有人窃取我的秘密!

我有一个控制器类,用于使用 Modbus 协议通过 TCP 与设备进行通信。我使用 NModbus 库。

以下是控制器类实现的接口:

public interface CoilReader
{
    bool[] Read(ushort startAddress, ushort numberOfCoils);
}

public interface CoilWriter
{
    void WriteSingle(ushort address, bool value);

    void WriteMultiple(ushort startAddress, bool[] values);
}

public interface HoldingRegisterReader
{
    ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}

public interface HoldingRegisterWriter
{
    void WriteSingle(ushort address, ushort value);

    void WriteMultiple(ushort startAddress, ushort[] values);
}

public interface InputReader
{
    bool[] Read(ushort startAddress, ushort numberOfCoils);
}

public interface InputRegisterReader
{
    ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}

public interface ConnectionInfo
{
    string IP { get; set; }
}

这里是控制器类。

using System;
using System.Net.Sockets;
using System.Reflection;
using global::Modbus.Device;

public class Controller
    : ConnectionInfo,
      HoldingRegisterReader,
      InputRegisterReader,
      CoilReader,
      InputReader,
      CoilWriter,
      HoldingRegisterWriter
{
    static Controller()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
    }

    public virtual string IP
    {
        get
        {
            return this.ip;
        }

        set
        {
            this.ip = value;
        }
    }

    public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }
    }

    public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadInputRegisters(startAddress, numberOfRegisters);
        }
    }

    public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadCoils(startAddress, numberOfCoils);
        }
    }

    public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
    {
        using (var connection = this.createDeviceConnection())
        {
            return connection.ReadInputs(startAddress, numberOfInputs);
        }
    }

    public virtual void WriteSingleCoil(ushort address, bool value)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteSingleCoil(address, value);
        }
    }

    public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteMultipleCoils(startAddress, values);
        }
    }

    public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteSingleRegister(address, value);
        }
    }

    public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
    {
        using (var connection = this.createDeviceConnection())
        {
            connection.WriteMultipleRegisters(startAddress, values);
        }
    }

    string ConnectionInfo.IP
    {
        get
        {
            return this.IP;
        }

        set
        {
            this.IP = value;
        }
    }

    ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
    {
        return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
    }

    ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
    {
        return this.ReadInputRegisters(startAddress, numberOfRegisters);
    }

    bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
    {
        return this.ReadCoils(startAddress, numberOfCoils);
    }

    bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
    {
        return this.ReadInputs(startAddress, numberOfInputs);
    }

    void CoilWriter.WriteSingle(ushort address, bool value)
    {
        this.WriteSingleCoil(address, value);
    }

    void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
    {
        this.WriteMultipleCoils(startAddress, values);
    }

    void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
    {
        this.WriteSingleHoldingRegister(address, value);
    }

    void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
    {
        this.WriteMultipleHoldingRegisters(startAddress, values);
    }

    private ModbusIpMaster createDeviceConnection()
    {
        const int port = 502;
        var client = new TcpClient();
        client.BeginConnect(this.ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
        if (!client.Connected)
        {
            throw new Exception("Cannot connect to " + this.ip + ":" + port);
        }

        return ModbusIpMaster.CreateIp(client);
    }

    private static Assembly loadEmbeddedAssembly(string name)
    {
        if (name.EndsWith("Retargetable=Yes"))
        {
            return Assembly.Load(new AssemblyName(name));
        }

        var container = Assembly.GetExecutingAssembly();
        var path = new AssemblyName(name).Name + ".dll";

        using (var stream = container.GetManifestResourceStream(path))
        {
            if (stream == null)
            {
                return null;
            }

            var bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
            return Assembly.Load(bytes);
        }
    }

    private string ip;
}

在与包含此类及其接口的库相同的解决方案中的 Tests 项目中创建的测试中,我没有收到以下错误。但是,在使用该库的不同解决方案中的项目中,我得到以下信息:

------ Test started: Assembly: CareControls.IvisHmi.Tests.dll ------

Unknown .NET Framework Version: v4.5.1
Test 'CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName' failed:     FakeItEasy.Core.FakeCreationException : 
Failed to create fake of type "CareControls.Modbus.Tcp.Controller".

Below is a list of reasons for failure per attempted constructor:
  No constructor arguments failed:
    No default constructor was found on the type CareControls.Modbus.Tcp.Controller.

If either the type or constructor is internal, try adding the following attribute to the assembly:
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]


at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithResolvedConstructors(Type typeOfFake, String reasonForFailureOfUnspecifiedConstructor, IEnumerable`1 resolvedConstructors)
at FakeItEasy.Creation.FakeObjectCreator.TryCreateFakeWithDummyArgumentsForConstructor(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, String failReasonForDefaultConstructor, Boolean throwOnFailure)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, FakeOptions options)
at FakeItEasy.Creation.DefaultFakeCreatorFacade.CreateFake[T](Action`1 options)
at FakeItEasy.A.Fake[T]()
Presenters\ModbusTcpTogglePresenterTests.cs(22,0): at CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName()

0 passed, 1 failed, 0 skipped, took 0.93 seconds (xUnit.net 1.9.2 build 1705).

这是测试:

namespace CareControls.IvisHmi.Tests.Presenters
{
    using CareControls.IvisHmi.Framework;
    using CareControls.IvisHmi.Presenters;
    using CareControls.IvisHmi.UI;
    using CareControls.Modbus.Tcp;
    using FakeItEasy;
    using Ploeh.AutoFixture;
    using Xunit;

    public class ModbusTcpTogglePresenterTests
    {
        [Fact]
        public void FactMethodName()
        {
            A.Fake<Controller>();
        }
    }
}

为什么 FakeItEasy 不认为类上有默认构造函数?

很抱歉,我发布了大量帖子,但要求我包含代码。

编辑:如果我在A.Fake&lt;Controller&gt;() 行之前添加new Controller(),则测试通过:

namespace CareControls.IvisHmi.Tests.Presenters
{
    using CareControls.IvisHmi.Framework;
    using CareControls.IvisHmi.Presenters;
    using CareControls.IvisHmi.UI;
    using CareControls.Modbus.Tcp;
    using FakeItEasy;
    using Ploeh.AutoFixture;
    using Xunit;

    public class ModbusTcpTogglePresenterTests
    {
        [Fact]
        public void FactMethodName()
        {
            new Controller();
            A.Fake<Controller>();
        }
    }
}

【问题讨论】:

  • 你能分享你的代码吗?
  • 我们来看看SomeClass和AnotherClass
  • 错误消息抱怨AnotherClass 没有构造函数,但您在帖子中提到 "SomeClass 确实有默认构造函数!"。你检查过AnotherClass吗?
  • 我想我在将实际代码翻译成供公众查看的代码时搞砸了。我将冒险并发布生产代码。
  • 你能不能试着从代码中删除一些零碎的东西,或者甚至可以复制你可以乱用的类,然后一步一步删除一些零碎的东西,直到它起作用还是留下一些小东西?

标签: c# fakeiteasy


【解决方案1】:

据此:

Faking/mocking an interface gives "no default constructor" error, how can that be?

有一个错误可以给出错误的异常消息。他们说,在那种情况下,仍然应该有一些东西导致异常被抛出。

我的建议:

  • 添加显式默认构造函数有帮助吗?
  • 能否正常成功实例化类?
  • 你能用反射或Activator.CreateInstance成功实例化类吗?

【讨论】:

  • Activator.CreateInstance(typeof(Controller))new Controller() 都有效。此外,当我在A.Fake&lt;Controller&gt;() 行之前添加这些行之一时,测试通过了!到底是怎么回事?
  • @SamPearson 可能是程序集尚未加载,因此 FakeItEasy 使用的反射找不到类。通过在执行“伪造”之前在代码中使用该类,您正在加载程序集,并且类型变得可见。不是 100% 肯定,只是猜测,但我之前也遇到过类似的问题。
  • 引用类型,就像在&lt;Class&gt; 中所做的那样,应该足以加载程序集。
  • 最近的 FakeItEasy 版本针对“错误错误”问题进行了改进。 1.14.0 及更高版本应该提供更好的提示。很好的建议,顺便说一句。
【解决方案2】:

静态构造函数与公共构造函数不同。您确定在定义静态构造函数时编译器会生成默认的公共构造函数吗?我不会想到你会得到两个。如果你use ILDASM to reverse engineer the assembly,它会显示吗?

静态构造函数很可能被称为“懒惰”,因此当 FakeItEasy 试图通过反射类型来创建假时,静态构造函数还没有被调用。这可以解释为什么在创建假之前实例化类型时它会起作用。

【讨论】:

  • 快速测试表明,是的,即使存在显式静态构造函数,也会生成默认构造函数。
  • 静态构造函数被延迟调用,所以这可能是问题所在。
【解决方案3】:

总结 Sam Pearson 使用 FakeItEasy 1.18.0 以及 MSDN 文档收集的信息,问题是由无法加载 Modbus 程序集时引发的异常引起的。这很有趣,因为Controller 的静态构造函数包含处理此故障的代码,因此它一定没有被执行。调用静态构造函数

…在创建第一个实例或引用任何静态成员之前自动初始化类。1

但是,FakeItEasy 并没有故意做这两件事。 DynamicProxy 似乎也没有。

我的猜测是访问该类以便可以伪造它会触发 Modbus 程序集加载,而不会触发静态构造函数。

但是,FakeItEasy 的程序集扫描可能会触发程序集加载。
这可以通过添加一个伪造其他东西的测试来检查,比如ICollection,然后运行它。
如果错误仍然存​​在,那么就是目录扫描正在执行此操作。在这种情况下,您可以使用 FakeItEasy 1.18.0 中添加的新引导程序来禁用目录扫描。 我们还没有官方文档,因为有一个 ongoing discussion about changing the scanning mechanism a little,但我确实发布了关于 how to disable the scanning of on-disk assemblies 的信息。 如果 FakeItEasy 的扫描触发加载,并且禁用扫描将程序集加载延迟到调用静态构造函数之后,这可能是最简单的事情。

其他可能的解决方法:

  1. 首先实例化 Controller 是一种解决方法(如前所述)
  2. 伪造Controller 实现的众多接口中的一个(或全部)而不是伪造Controller(我个人的偏好是伪造接口,而不是类,但有时需要)
  3. 如果可能的话,也许最好的办法是解决程序集加载失败的原因,但我敢打赌,如果这是一个选项,静态构造函数不会包含它所包含的代码。

【讨论】:

  • 嗨布莱尔,非常感谢您的回答。程序集加载失败的原因是我将 NModbus 程序集嵌入到包含 Controller 的程序集中。
  • 我尝试创建一个NoExternalScanningBootstrapper,就像您在帖子中提到的那样,但我不知道如何配置 FakeItEasy 以使用该引导程序。
  • 如果有更好的地方来处理另一个程序集中的嵌入式程序集的程序集负载,请告诉我!对于可执行文件,我只是在入口点订阅,但这是我能得到的最接近这个类的“入口点”的地方。
  • 我刚刚在GetAssemblyFileNamesToScanForExtensions 添加了一个断点,它正在到达,但我仍然得到FakeCreationException
  • 我从 Cheeso 的回答中了解了如何从不可执行的程序集中加载嵌入式程序集:stackoverflow.com/questions/222655/…
【解决方案4】:

我通过将createDeviceConnection() 方法转换为NModbusDeviceConnection 一次性类解决了这个问题。在NModbusDeviceConnection 的静态构造函数中,订阅了AssemblyResolve 事件。现在我可以在不触发Modbus 程序集负载的情况下创建Controller 的假货。

详情请看NModbusDeviceConnection类:

// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="NModbusDeviceConnection.cs" company="Care Controls">
//   Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------

namespace CareControls.Modbus.Tcp.Internal
{
    using System;
    using System.Net.Sockets;
    using System.Reflection;
    using global::Modbus.Device;

    internal sealed class NModbusDeviceConnection : IDisposable
    {
        static NModbusDeviceConnection()
        {
            AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
        }

        public NModbusDeviceConnection(string ip)
        {
            const int port = 502;
            var client = new TcpClient();
            client.BeginConnect(ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
            if (!client.Connected)
            {
                throw new Exception("Cannot connect to " + ip + ":" + port);
            }

            this.connection = ModbusIpMaster.CreateIp(client);
        }

        public ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            return this.connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }

        public ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            return this.connection.ReadInputRegisters(startAddress, numberOfRegisters);
        }

        public bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
        {
            return this.connection.ReadCoils(startAddress, numberOfCoils);
        }

        public bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
        {
            return this.connection.ReadInputs(startAddress, numberOfInputs);
        }

        public void WriteSingleCoil(ushort address, bool value)
        {
            this.connection.WriteSingleCoil(address, value);
        }

        public void WriteMultipleCoils(ushort startAddress, bool[] values)
        {
            this.connection.WriteMultipleCoils(startAddress, values);
        }

        public void WriteSingleHoldingRegister(ushort address, ushort value)
        {
            this.connection.WriteSingleRegister(address, value);
        }

        public void WriteMultipleHoldingRegisters(ushort address, ushort[] values)
        {
            this.connection.WriteMultipleRegisters(address, values);
        }

        public void Dispose()
        {
            if (this.connection != null)
            {
                this.connection.Dispose();
            }
        }

        private static Assembly loadEmbeddedAssembly(string name)
        {
            if (name.EndsWith("Retargetable=Yes"))
            {
                return Assembly.Load(new AssemblyName(name));
            }

            var container = Assembly.GetExecutingAssembly();
            var path = new AssemblyName(name).Name + ".dll";

            using (var stream = container.GetManifestResourceStream(path))
            {
                if (stream == null)
                {
                    return null;
                }

                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);
                return Assembly.Load(bytes);
            }
        }

        private readonly ModbusIpMaster connection;
    }
}

这里是修改后的Controller 类:

// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="Controller.cs" company="Care Controls">
//   Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------

namespace CareControls.Modbus.Tcp
{
    using System;
    using CareControls.Modbus.Tcp.Internal;

    public class Controller
        : ConnectionInfo,
          HoldingRegisterReader,
          InputRegisterReader,
          CoilReader,
          InputReader,
          CoilWriter,
          HoldingRegisterWriter
    {
        public Controller()
        {
            this.newDeviceConnection = () => new NModbusDeviceConnection(this.IP);
        }

        public virtual string IP { get; set; }

        public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
            }
        }

        public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadInputRegisters(startAddress, numberOfRegisters);
            }
        }

        public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadCoils(startAddress, numberOfCoils);
            }
        }

        public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
        {
            using (var connection = this.newDeviceConnection())
            {
                return connection.ReadInputs(startAddress, numberOfInputs);
            }
        }

        public virtual void WriteSingleCoil(ushort address, bool value)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteSingleCoil(address, value);
            }
        }

        public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteMultipleCoils(startAddress, values);
            }
        }

        public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteSingleHoldingRegister(address, value);
            }
        }

        public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
        {
            using (var connection = this.newDeviceConnection())
            {
                connection.WriteMultipleHoldingRegisters(startAddress, values);
            }
        }

        string ConnectionInfo.IP
        {
            get
            {
                return this.IP;
            }

            set
            {
                this.IP = value;
            }
        }

        ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
        {
            return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
        }

        ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
        {
            return this.ReadInputRegisters(startAddress, numberOfRegisters);
        }

        bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
        {
            return this.ReadCoils(startAddress, numberOfCoils);
        }

        bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
        {
            return this.ReadInputs(startAddress, numberOfInputs);
        }

        void CoilWriter.WriteSingle(ushort address, bool value)
        {
            this.WriteSingleCoil(address, value);
        }

        void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
        {
            this.WriteMultipleCoils(startAddress, values);
        }

        void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
        {
            this.WriteSingleHoldingRegister(address, value);
        }

        void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
        {
            this.WriteMultipleHoldingRegisters(startAddress, values);
        }

        private readonly Func<NModbusDeviceConnection> newDeviceConnection;
    }
}

【讨论】:

    猜你喜欢
    • 2016-07-18
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-19
    • 1970-01-01
    • 2018-03-09
    相关资源
    最近更新 更多