【问题标题】:Internal property setters in C#C# 中的内部属性设置器
【发布时间】:2012-11-28 20:54:07
【问题描述】:

我正在尝试找出解决此问题的好方法。我有一个实现 ICustomer 接口的 Customer 类。这个接口有很多属性:

public interface ICustomer
{

   string FirstName {get; set;}
   string LastName  {get; set;}
}

我只希望某些类能够设置这些属性;即项目中的那些类。于是我想到了做二传手internal:

public class Customer : ICustomer
{

   string FirstName {get; internal set;}
   string LastName  {get; internal set;}
}

我想在接口中将该设置器标记为内部的,因此不可能有人实现 ICustomer 并且程序集之外的人会修改这些属性。有没有好的方法来做到这一点?

【问题讨论】:

    标签: c# properties setter internal


    【解决方案1】:

    界面中的属性应该是只读的。实现接口的具体类有一个setter是可以接受的,即使接口中没有定义。

    public interface ICustomer
    {
       string FirstName { get; }
       string LastName  { get; }
    }
    
    public class Customer : ICustomer
    {
       public string FirstName { get; internal set; }
       public string LastName  { get; internal set; }
    }
    

    如果通过接口公开设置器真的很重要,而不是让接口完全只读,您可以使用以下内容:

    public interface IReadCustomer
    {
        string FirstName { get; }
        string LastName { get; }
    }
    
    internal interface IWriteCustomer
    {
        string FirstName { set; }
        string LastName { set; }
    }
    
    internal interface IReadWriteCustomer : IReadCustomer, IWriteCustomer
    { }
    
    public class Customer : IReadWriteCustomer
    {
        private string _firstName;
        private string _lastName;
    
        public string FirstName
        {
            get { return _firstName; }
            internal set { _firstName = value; }
        }
        public string LastName
        {
            get { return _lastName; }
            internal set { _lastName = value; }
        }
    
        string IReadCustomer.FirstName
        {
            get { return FirstName; }
        }
    
        string IReadCustomer.LastName
        {
            get { return LastName; }
        }
    
        string IWriteCustomer.FirstName
        {
            set { FirstName = value; }
        }
    
        string IWriteCustomer.LastName
        {
            set { LastName = value; }
        }
    }
    

    【讨论】:

    • 感谢您的帮助。但是,如果我这样做,并且消费类使用 ICustomer,它就无法访问 setter。
    • @larryq 这就是重点,因为接口没有提交给 setter。如果你想实现第二个内部接口来添加你可以的 setter。我经常有一系列的接口:这是一个真实的例子:IDimensionedMap 只提交二维实体的维度; IReadMap 具有可读属性,IReadMap : IDimensionedMap 用于只读或计算映射,然后是 IWriteMap : IReadMap。但这是一个复杂的可扩展实用程序类家族。 (如果您好奇,这些是 2d Hilbert 空间,而不是字典。)
    • @larryq 虽然我不确定你是否需要它,但我添加了一个替代实现,其中接口定义了一个内部 set 方法。
    • 谢谢先生们,这些都是很棒的建议,非常感谢。
    【解决方案2】:

    我想在接口中将该设置器标记为内部的,因此不可能有人实现 ICustomer 并且程序集之外的人会修改这些属性。有什么好办法吗?

    没有。不幸的是,属性成员始终是公开的。此外,在接口上指定部分属性的访问级别搞乱会很痛苦,IIRC。你可以做的是:

    public interface ICustomer
    {
        string FirstName { get; }
        string SecondName { get; }
    }
    
    internal interface ICustomerWithSetMethods : ICustomer
    {
        void SetFirstName(string name);
        void SetLastName(string name);
    }
    
    public class Customer : ICustomerWithSetMethods
    

    然后从外部看起来Customer只实现了ICustomer,但从内部你的代码会看到它实现了ICustomerWithSetMethods

    不幸的是,如果您的 API 需要声明任何您希望真正仅声明返回类型为 ICustomer 的公共方法,那么这并不好用,但您实际上会知道总是ICustomerWithSetMethods

    假设您仍然希望允许多个实现,您可能会改用抽象类:

    public abstract class CustomerBase
    {
        public abstract string FirstName { get; }
        public abstract string LastName { get; }
    
        internal abstract void SetFirstName(string name);
        internal abstract void SetLastName(string name);
    }
    

    现在我们有点奇怪,在程序集之外没有人可以扩展您的 CustomerBase,因为他们必须重写他们甚至无法看到的抽象方法 - 但是这确实意味着您可以在 API 的任何地方使用CustomerBase

    这是我们最终在 Noda Time 中为日历系统采用的方法 - 我是 blogged about it 当我第一次提出计划时。我一般更喜欢接口而不是抽象类,但这里的好处是显着的。

    【讨论】:

    • 您错过了 FirstName 和 SecondName 的类型和范围
    • @JonSkeet 我同意你的方法,但是当随着时间的推移可维护性成为问题时,我通常会同时实现接口和抽象基类。它有点额外的输入,但像 Resharper 这样的工具使它更容易,这意味着当你有一个复杂的对象系列时,你可以拥有多个抽象实现,也许会在你自己的代码库之外扩展。
    • @SAJ14SAJ:这不是额外输入的问题 - 我喜欢能够在此之上放置一个界面;问题是您不能在接口中声明内部成员。这就是这个问题的重点。 (在 Noda Time 的情况下,我真的不介意第 3 方不能添加新的实现。我们故意隐藏了很多 必须 必须在抽象成员签名中的类型。)
    • @JonSkeet 对不起,我错过了那个微妙之处。我认为有第二个内部接口扩展了第一个公共接口,这是合法的。一般来说,C# 比任何其他经典对象语言更接近于在该范式中表达我想要的东西!在某些 CLR 库类中也可以看到我对这些情况的妥协——为接口的不受支持的部分抛出 IllegalOperationException,提供让您首先测试的属性,并记录其中的哔哔声。跨度>
    • @SAJ14SAJ:不幸的是,当您需要在成员签名中引用内部类型时,这无济于事......我似乎确实比其他人更容易遇到这样的极端情况!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 2015-08-23
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 2010-09-30
    相关资源
    最近更新 更多