只写属性是否具有实际应用?

时间:2010-02-06 17:05:36

标签: oop properties language-agnostic

我不知道为什么我开始考虑这个问题,但现在我似乎无法停止。

在C#中 - 可能还有很多其他语言,我记得Delphi曾经让你这么做 - 编写这种语法是合法的:

class WeirdClass
{
    private void Hello(string name)
    {
        Console.WriteLine("Hello, {0}!", name);
    }

    public string Name
    {
        set { Hello(name); }
    }
}

换句话说,该属性有 setter 但没有 getter ,它是只写

我想我无法想到为什么这应该是非法,但我从来没有真正看到它在野外,我似乎有一些相当精彩/恐怖的代码狂野的。这似乎是一种代码味道;似乎编译器应该给我一个警告:

  

CS83417:属性'名称'似乎完全无用且愚蠢。糟糕的程序员!考虑用方法替换。

但也许我只是没有做这么长时间,或者为了看到任何有效使用这种结构的例子而在一个太狭窄的领域工作。

是否存在只写属性的现实示例,这些示例要么无法通过直接方法调用替换,要么会变得不那么直观?

15 个答案:

答案 0 :(得分:16)

代码分析(又名FxCop)确实为您提供诊断:

  

CA1044:Microsoft.Design:因为   属性'WeirdClass.Name'是只写的,   要么添加一个属性getter   可访问性大于或   等于它的二传手或转换它   属性成为一种方法。

答案 1 :(得分:14)

我对这个问题的第一反应是:“java.util.Random#setSeed方法怎么样?”

我认为只写属性在几种情况下都很有用。例如,当您不想公开内部表示(封装)时,允许更改对象的状态。 java.util.Random是这种设计的一个很好的例子。

答案 2 :(得分:12)

只写属性实际上非常有用,我经常使用它们。这完全是关于encapsulation - 限制对对象组件的访问。您经常需要为需要在内部使用的类提供一个或多个组件,但没有理由让其他类可以访问它们。这样做只会让你的课更容易混淆(“我会使用这个getter或这种方法吗?”),更可能是你的课程被篡改或绕过了真正的目的。

有关此问题的有趣讨论,请参阅"Why getter and setter methods are evil"。我不像文章的作者那样关心它,但我认为这是一件好事。我通常使用setter但很少使用getter。

答案 3 :(得分:8)

我在XNA项目中有类似于以下的代码。正如您所看到的, Scale 是只写的,它很有用且(合理地)直观,并且读取属性( get )对它没有意义。当然它可以用方法替换,但我喜欢语法。

public class MyGraphicalObject
{
    public double ScaleX { get; set; }
    public double ScaleY { get; set; }
    public double ScaleZ { get; set; }

    public double Scale { set { ScaleX = ScaleY = ScaleZ = value; } }

    // more...
}

答案 4 :(得分:3)

只写属性的一个用途是支持setter依赖注入,它通常用于可选参数。

假设我上课了:

public class WhizbangService {
    public WhizbangProvider Provider { set; private get; }
}

WhizbangProvider不打算被外界访问。我永远不想与service.Provider互动,这太复杂了。我需要一个像WhizbangService这样的类来充当外观。然而对于二传手,我可以这样做:

service.Provider = new FireworksShow();
service.Start();

该服务开始烟花汇演。或者你可能更喜欢看水和灯光表演:

service.Stop();
service.Provider = new FountainDisplay(new StringOfLights(), 20, UnitOfTime.Seconds);
service.Start();

依旧......

如果在基类中定义属性,这将变得特别有用。如果为此属性选择了构造注入,则需要在任何派生类中编写构造函数重载。

public abstract class DisplayService {
    public WhizbangProvider Provider { set; private get; }
}

public class WhizbangService : DisplayService  { }

这里,构造函数注入的替代方法是:

public abstract class DisplayService {
    public WhizbangProvider Provider;

    protected DisplayService(WhizbangProvider provider) {
       Provider = provider ?? new DefaultProvider();
    }
}

public class WhizbangService : DisplayService  { 
    public WhizbangService(WhizbangProvider provider) 
      : base(provider) 
    { }
}

我认为这种方法比较麻烦,因为你需要对类的一些内部工作进行处理,具体来说,如果你将null传递给构造函数,你将得到一个合理的默认值。

答案 5 :(得分:2)

只要你不应该阅读你写的内容,制作只写的内容就很有用。

例如,在屏幕上绘制内容时(这正是桌面窗口管理器在Windows中的作用):
你当然可以绘制到一个屏幕,但你永远不需要回读数据(更不用说希望获得与以前相同的设计)。

现在,无论只写属性是否有用(与方法相反),我不确定它们的使用频率。我想你可以想象一个具有“BackgroundColor”属性的情况,写入它会设置屏幕的背景颜色,但阅读没有意义(必然)。
所以我不确定那个部分,但总的来说我只想指出用例,用于你只写数据的情况,而且从不读它。

答案 6 :(得分:2)

在MVP模式中,通常在视图上编写带有setter的属性(不需要getter) - 每当演示者设置内容时,属性将使用该值来更新某些UI元素。

请参阅here进行小型演示:

public partial class ShowMeTheTime : Page, ICurrentTimeView
{
    protected void Page_Load(object sender, EventArgs e) 
    {
        CurrentTimePresenter presenter = new CurrentTimePresenter(this);
        presenter.InitView();
    }

    public DateTime CurrentTime 
    {
        set { lblCurrentTime.Text = value.ToString(); }
    }
}

演示者InitView方法只是设置属性的值:

public void InitView() 
{
    view.CurrentTime = DateTime.Now;
}

答案 7 :(得分:2)

虽然.NET design guidelines建议使用方法(" SetMyWriteOnlyParameter")而不是只写属性,但我发现只读属性在从序列化表示创建链接对象时很有用(来自数据库)。

我们的应用代表了油田生产系统。我们有整个系统("模型"对象)和各种水库,井,节点,组等对象。

首先从数据库创建和读取模型 - 其他对象需要知道它们属于哪个模型。但是,模型需要知道哪个较低的对象代表销售总额。将此信息存储为Model属性是有意义的。如果我们不想对模型信息进行两次读取,我们需要能够在创建之前读取Sales对象的名称。然后,我们随后设置" SalesObject"变量指向实际对象(例如,用户对此对象名称的任何更改都不会导致问题)

我们更喜欢使用只写属性 - ' SalesObjectName =" TopNode"' - 而不是方法 - ' SetSalesObjectName(" TopNode") - 因为在我们看来,后者表明SalesObject存在。

这是一个小问题,但足以让我们想要使用只写属性。

答案 8 :(得分:1)

就我而言,他们没有。每次我使用只写属性作为快速黑客时,我后来都后悔了。通常我最终会得到一个构造函数或一个完整的属性。

当然,我试图证明是消极的,所以也许有些东西我不知道了。

答案 9 :(得分:1)

我也不能停止思考这个问题。我有一个“只写”属性的用例。我看不出好的方法。

我想构建一个C#属性,该属性派生自ASP.NET MVC应用程序的AuthorizeAttribute。我有一个服务(比如,IStore),它返回的信息有助于决定当前用户是否应该被授权。构造函数注入不起作用,因为

public AllowedAttribute: AuthorizeAttribute
{
    public AllowedAttribute(IStore store) {...}
    private IStore Store { get; set; }
    ...
}

使store成为位置属性参数,但IStore不是有效的属性参数类型,并且编译器不会构建使用它注释的代码。我被迫退回Property Setter Injection。

public AllowedAttribute: AuthorizeAttribute
{
    [Inject] public IStore Store { private get; set; }
    ...
}

除了Property Setter而不是Constructor Injection的所有其他坏处外,该服务是一个只写属性。我不得不将setter公开给那些不需要了解实现细节的客户端。它也不会让任何人有任何好处让客户看到吸气剂。

我认为依赖注入的好处胜过针对此场景的只写属性的指导原则,除非我遗漏了某些内容。

答案 10 :(得分:0)

不,我可以'想象他们无法被替换的任何情况,尽管可能会有人认为它们更具可读性。

假设案例:

CommunicationDevice.Response = "Hello, World" 

而不是

CommunicationDevice.SendResponse("Hello, World")

主要工作是执行IO副作用或验证。

有趣的是,VB .NET甚至为这种奇怪的财产提供了自己的关键字;)

Public WriteOnly Property Foo() As Integer
   Set(value As Integer)
     ' ... '
   End Set
End Property

尽管来自外部的许多“只写”属性实际上都有私有的getter。

答案 11 :(得分:0)

我最近处理过一个处理密码的应用程序。 (请注意,我并不是说以下是个好主意;我只是在描述我的所作所为。)

我有一个类HashingPassword,其中包含密码。构造函数将密码作为参数并将其存储在私有属性中。给定其中一个对象,您可以获取密码的盐渍哈希,或者根据给定的盐渍哈希检查密码。当然,没有办法从HashingPassword对象中检索密码。

那么我还有其他一些对象,我不记得它是什么;让我们假装它是一个受密码保护的香蕉。 Banana类有一个名为Password的仅限属性,它根据给定值创建HashingPassword并将其存储在Banana的私有属性中。由于password的{​​{1}}属性是私有的,因此无法为此属性编写getter。

那为什么我有一个名为HashingPassword的仅限属性而不是名为Password的方法?因为它有意义。实际上,效果是设置SetPassword的密码,如果我想设置Banana对象的密码,我希望通过设置属性而不是通过调用来实现一种方法。

使用名为Banana的方法不会有任何重大缺点。但我也没有看到任何明显的优势。

答案 12 :(得分:0)

我知道这已经存在了很长时间,但我遇到了它并且有一个有效的(imho)用例:

当您将参数发布到来自ajax的webapi调用时,您只需尝试填写参数类的属性并包含验证或其他任何内容。

public int MyFancyWepapiMethod([FromBody]CallParams p) {
    return p.MyIntPropertyForAjax.HasValue ? p.MyIntPropertyForAjax.Value : 42;
}

public class CallParams
{
    public int? MyIntPropertyForAjax;

    public object TryMyIntPropertyForAjax
    {
        set
        {
            try { MyIntPropertyForAjax = Convert.ToInt32(value); }
            catch { MyIntPropertyForAjax = null; }
        }
    }
}

在JavaScript方面,您只需填写参数,包括验证:

var callparameter = {
    TryMyIntPropertyForAjax = 23
}

在这个例子中是安全的,但是如果你处理userinput,它可能不确定你是否有一个有效的intvalue或类似的东西。

答案 13 :(得分:0)

我刚刚在编写从 JSON 数据库 (Firebase) 读取数据的程序时遇到了这种情况。它使用 Newtonsoft 的 Json.NET 来填充对象。数据是只读的,即一旦加载它们就不会改变。此外,对象只会反序列化,不会再次序列化。可能有更好的方法,但这个解决方案对我来说看起来很合理。

using Newtonsoft.Json;
// ...

public class SomeDatabaseClass
{
    // JSON object contains a date-time field as string
    [JsonProperty("expiration")]
    public string ExpirationString
    {
        set
        {
            // Needs a custom parser to handle special date-time formats
            Expiration = Resources.CustomParseDateTime(value);
        }
    }
    
    // But this is what the program will effectively use.
    // DateTime.MaxValue is just a default value
    [JsonIgnore]
    public DateTime Expiration { get; private set; } = DateTime.MaxValue;
    
    // ...
}

答案 14 :(得分:-1)

嗯,从技术上讲,Setter-only属性本身没有任何问题。 Setter可以做一些验证,也许其他一些方法 - 公共或私人 - 可以依赖它。

例如:

class SomeClass {
    private int _someVar;

    SomeClass(int someVar) {
        if(someVar > 0) _someVar = someVar;
    }

    public int SomeVar {
        set { if(value > 0) _someVar = value; }
    }

     public string SomeFunction(){
       return "This is an important function that uses _someVar " + _someVar;
     }
}

因此声明本身可能是合法且有效的。你的方法体虽然有很大的代码味道,但这并不是编译器的工作。