如何创建只读 属性 暴露给 COM?

How to create a read-only property exposed to COM?

我有一个 C# class,我想通过 COM 将其公开给 VB6。问题是这需要 class 有一个默认的构造函数。因此,我必须向客户端公开 setter,以便可以设置这些属性。

例如:

[Guid("B1E17DF6-9578-4D24-B578-9C70979E2F05")]
public interface _Class1
{

    [DispId(2)]
    string Message { get; set; }

    [DispId(1)]
    string TestingAMethod();
}

[Guid("197A7A57-E59F-41C9-82C8-A2F051ABA53C")]
[ProgId("Project.Class1")]
[ClassInterface(ClassInterfaceType.None)]
public class Class1 : _Class1
{
    public string Message { get; set; }

    public Class1() { } //default constructor for COM

    public Class1(string message)
    {
        this.Message = message;
    }

    public string TestingAMethod()
    {
        return "Hello World";
    }
}

通常,我会将 属性 声明为:

public string Message { get; private set; }

但这显然行不通,因为 COM 不能使用接受参数的构造函数。

所以,问题是:

如何确保 属性 在不使用 private setreadonly 的情况下仅设置一次?

它不漂亮,也没有暴露它是 "read-only" 的事实,但是像私人跟踪 bool 这样的东西有用吗?

[Guid("197A7A57-E59F-41C9-82C8-A2F051ABA53C")]
[ProgId("Project.Class1")]
[ClassInterface(ClassInterfaceType.None)]
public class Class1 : _Class1
{
    private bool _isMessageSet = false;
    private string _message;

    public string Message 
    { 
        get { return _message; }
        set
        {
            if (!_isMessageSet)
            {
                _message = value;
                _isMessageSet = true;
            }
        }
    }

    public Class1() { } //default constructor for COM

    public Class1(string message)
    {
        this.Message = message;
    }
}

根据this article on Code Project

If the method call fails or business logic validation fails, the .NET component is expected to raise an exception. This exception usually has a failure HRESULT assigned to it and an Error description associated with it. The CCW gleans out details such as the Error Code, Error message etc. from the .NET Exception and provides these details in a form that can be consumed by the COM client.

强调我的。

所以,我们可以检查一下是否设置了私有字段。如果是,则抛出异常。

private string message;
public string Message {
    get { return message; }
    set
    {
        if (message != null)
        {
            throw new ReadOnlyPropertyException("Class1.Message can not be changed once it is set.");
        }
        message = value;
    }
}

从以下 VB6/VBA 代码中使用它

Dim cls As New Rubberduck_SourceControl.Class1
cls.Message = "Hello"
Debug.Print cls.Message

cls.Message = "Goodbye" 'we expect a read only error here
Debug.Print cls.Message

导致出现错误。

有效地使 属性 设置为只读。


但这会导致客户端遇到运行时错误。这里的解决方案是按照 的建议创建一个 class 工厂。

Understanding the COM philosophy is important. COM provides a class factory to create objects but it doesn't support passing arbitrary arguments. Which is why you always need a default constructor. Well, no problem, just create your own factory. Hide Class1 and write a Class1Factory class. With a CreateClass1() method that returns _Class1. It can take any arguments you need.

所以,我按照我最初想要的方式实现了 class。 (没有默认构造函数和 private set 属性。重要的是要注意接口只有 get 用于 属性。

[Guid("B1E17DF6-9578-4D24-B578-9C70979E2F05")]
public interface _Class1
{

    [DispId(2)]
    string Message { get; }

    [DispId(1)]
    string TestingAMethod();
}

[Guid("197A7A57-E59F-41C9-82C8-A2F051ABA53C")]
[ProgId("Project.Class1")]
[ClassInterface(ClassInterfaceType.None)]
public class Class1 : _Class1
{
    public string Message { get; private set; }

    public Class1(string message)
    {
        this.Message = message;
    }

    public string TestingAMethod()
    {
        return "Hello World";
    }
}

然后我创建了一个 class 工厂,它 确实 有一个默认构造函数,并接受与 Class1 的构造函数相同的参数。

[Guid("98F2287A-1DA3-4CC2-B808-19C0BE976C08")]
public interface _ClassFactory
{
    Class1 CreateClass1(string message);
}

[Guid("C7546E1F-E1DB-423B-894C-CB19607972F5")]
[ProgId("Project.ClassFactory")]
[ClassInterface(ClassInterfaceType.None)]
public class ClassFactory : _ClassFactory
{
    public Class1 CreateClass1(string message)
    {
        return new Class1(message);
    }
}

所以,现在如果客户端尝试设置 Message 属性 ,它会得到一个 编译时错误。