How can I prevent callers from creating a C#/.Net 7 class object *EXCEPT* by calling a static "CreateInstance()" method?

huangapple go评论74阅读模式
英文:

How can I prevent callers from creating a C#/.Net 7 class object *EXCEPT* by calling a static "CreateInstance()" method?

问题

我有一个抽象基类 "UserFeedback",以及一些子类 "A" 和 "B"。

UserFeedback有一个静态 "CreateInstance()" 方法。

我想要 阻止 调用者直接实例化一个新的UserFeedback对象:

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }

    public abstract string GetTitle(string operation);
}

public class A : UserFeedback
{
    protected A(string componentType) : base(componentType)
    { }

    public override string GetTitle(string operation)
    {
        return "A Feedback";
    }
}

public class B : UserFeedback
{
    protected B(string componentType) : base(componentType)
    { }

    public override string GetTitle(string operation)
    {
        return "B Feedback";
    }
}

这给我在第32和34行编译错误:

错误 CS0122 'A.A(string)' 由于其保护级别而无法访问

在较新版本的C#/.NET中,是否有任何 "简单" 方法可以实现这一点?

还是我应该只是放弃,将A()和B()的构造函数设为 "public"?

英文:

I have an abstract base class "UserFeedback", and a couple of subclasses "A" and "B".

UserFeedback has a static "CreateInstance()" method.

I'd like to PREVENT callers from instantiating a new UserFeedback object directly:

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }

    public abstract string GetTitle(string operation);
}

public class A : UserFeedback
{
    protected A(string componentType) : base(componentType)
    { }

    public override string GetTitle(string operation)
    {
        return "A Feedback";
    }
}

public class B : UserFeedback
{
    protected B(string componentType) : base(componentType)
    { }

    public override string GetTitle(string operation)
    {
        return "B Feedback";
    }
}

This gives me compile errors on Line 32 and 34:

Error	CS0122	'A.A(string)' is inaccessible due to its protection level

Is there any "simple" way to accomplish this in newer versions of C#/.NET?

Or should I just punt, and make the constructors for A() and B() "public"?

答案1

得分: 2

你可以简单地将A和B设为私有 嵌套类型

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }

    public abstract string GetTitle(string operation);

    private class A : UserFeedback
    {
        public A(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "A Feedback";
        }
    }

    private class B : UserFeedback
    {
        public B(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "B Feedback";
        }
    }
}

[1]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/nested-types
英文:

You could simply make A and B private Nested Types

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }

    public abstract string GetTitle(string operation);

    private class A : UserFeedback
    {
        public A(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "A Feedback";
        }
    }

    private class B : UserFeedback
    {
        public B(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "B Feedback";
        }
    }
}

答案2

得分: 2

如果您真的不希望客户端创建派生类的实例,而只希望他们通过静态的CreateInstance方法进行操作,一个可能的方法是将派生类设为private,并将它们放在与UserFeedback相同的类中。如果您选择这种方式,您将无法将这些实例强制转换,因为它们将不可见。

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }
    public abstract string GetTitle(string operation);
    private class A : UserFeedback
    {
        public A(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "A Feedback";
        }
    }

    private class B : UserFeedback
    {
        public B(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "B Feedback";
        }
    }
}
英文:

If you really don't want clients to create instances of the derived classes and only want them to go through the static CreateInstance method, a possible way is to make your derived classes private and have them in the same class as Userfeedback. If you do go this route, you won't be able to cast to those instances as they won't be visible

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public static UserFeedback CreateInstance(string componentType)
    {
        switch (componentType)
        {
            case "A":
                return new A(componentType);  // Line 32
            case "B":
                return new B(componentType);  // Line 34
            default:
                throw new ArgumentException(String.Format("User Feedback Type: {0}", componentType));
        }
    }
    public abstract string GetTitle(string operation);
    private class A : UserFeedback
    {
        public A(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "A Feedback";
        }
    }

    private class B : UserFeedback
    {
        public B(string componentType) : base(componentType)
        { }

        public override string GetTitle(string operation)
        {
            return "B Feedback";
        }
    }
}

答案3

得分: 1

在你的代码中,子类 A 可以接收任何值,即使只有 A 是有效的,这看起来不太正确。为什么子类需要构造函数参数?如果不需要的话,为什么不将其设置为公共的,让调用者进行实例化?

如果以上说法正确,就让调用者直接实例化 AB 的实现。这些对象已经具有实例化所需的信息,不应该需要构造函数参数来告诉它们。

首先,让我们从抽象类中移除静态的 CreateInstance 方法。

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public abstract string GetTitle(string operation);
}

接下来,在子类中,将构造函数设置为公共的无参数构造函数,因为我们不需要外部信息来告诉我们什么是 AB。它们已经知道自己是什么,所以我们可以在基类构造函数调用中直接硬编码它们。

这还有一个额外的好处,就是当实现新的子类时,无需更新基类 UserFeedback

public class A : UserFeedback
{
    public A() : base(nameof(A))
    { }

    public override string GetTitle(string operation)
    {
        return "A Feedback";
    }
}

public class B : UserFeedback
{
    public B() : base(nameof(B))
    { }

    public override string GetTitle(string operation)
    {
        return "B Feedback";
    }
}
英文:

In your code, it doesn't seem 'correct' that the sub-class A can receive any value even though only A is valid. Why is the constructor parameter even needed in the sub-classes? If it's not needed then why not make it public and let callers instantiate it?

If the above it true, let callers instantiate the A and B implementations directly. These objects have the information they need to instantiate themselves and shouldn't need a constructor parameter to tell them.


First let's remove the static CreateInstance method from the abstract class.

public abstract class UserFeedback
{
    public string ComponentType { get; set; }
    public string UserName { get; set; }
    public string EMail { get; set; }

    protected UserFeedback(string componentType)
    {
        ComponentType = componentType;
    }

    public abstract string GetTitle(string operation);
}

Next, in the sub-classes, make the constructor a public parameter-less constructor as we don't need anything outside to tell us what A or B is. They already know what they are, so we can just hard code that to the base constructor call.

This also has the added benefit of not having to update the base UserFeedback class when new sub-classes are implemented.

I've used nameof() in my example to get rid of the strings.

public class A : UserFeedback
{
    public A() : base(nameof(A))
    { }

    public override string GetTitle(string operation)
    {
        return "A Feedback";
    }
}

public class B : UserFeedback
{
    public B() : base(nameof(B))
    { }

    public override string GetTitle(string operation)
    {
        return "B Feedback";
    }
}

答案4

得分: 0

有一个不同的选择。有时候,嵌套类是不可取的,或者以其他方式很难实现。

当出现这种情况时,protected internal 构造函数是您的朋友。只有您自己的程序集可以调用基本构造函数,因此只有您的程序集可以实例化它。

我习惯于看到 CreateInstance() 做一些很好的事情,比如根据 #if 或其他奇特的条件访问其中的一个实现。还有一种我不完全理解的机制,即在请求时可以加载特定的程序集,该程序集可以访问您的 protected external 构造函数,并且在编译时由您的程序集批准,因此其他人无法这样做。

英文:

There is a different option. Sometimes when this comes up, nested classes is undesirable or otherwise awkward to achieve.

When it happens, protected internal constructor is your friend. Only your own assembly can call the base constructor, so only your assembly can instantiate it.

I'm used to seeing CreateInstance() do nice things like access one of several implementations based on #if or other exotic things. There's also a mechanism I don't fully understand by which a specific assembly can be loaded upon request that can access your protected external constructor and that assembly is blessed by yours at compile time so nobody else can do it.

huangapple
  • 本文由 发表于 2023年6月16日 07:24:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76486071.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定