16

My manager has asked me if it is good practice to use a property with a setter, but no getter.

public class PropertyWrapper
{   
    private MyClass _field;

    public MyClass Property
    {
        set { _field = value; } 
    }

    public string FirstProperty
    {
        get { return _field.FirstProperty; } 
    }

    public string SecondProperty
    {
        get { return _field.SecondProperty; } 
    }
}

He would be using other properties to expose properties from a private field, set by this setter.

My suggestion was to just use a private field and set it in the constructor, which works fine in this scenario. If I needed to have a constructed object first (maybe even using polymorphism) I would still prefer a Load method, rather than a getter-less property.

But I'm interested. We're both very concerned by best-practices and try to make sure our code is standarised. Does anyone have any official articles about getter-less properties? Or better still - an example of this usage in the .NET Framework itself?

Connell
  • 13,925
  • 11
  • 59
  • 92
  • 2
    There is nothing inherently wrong with it, but it is uncommon. Why have a property for this if you can only set it? – Oded Mar 02 '12 at 15:26
  • 3
    See http://stackoverflow.com/questions/4695551/write-only-properties-whats-the-point – kaj Mar 02 '12 at 15:30
  • @KAJ thank you! I didn't find that one in my search at first. – Connell Mar 02 '12 at 15:42

10 Answers10

20

Official article: Design Guidelines for Developing Class Libraries -> Member Design Guidelines -> Property Design

Do not provide set-only properties.

If the property getter cannot be provided, use a method to implement the functionality instead. The method name should begin with Set followed by what would have been the property name. For example, AppDomain has a method called SetCachePath instead of having a set-only property called CachePath.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
10

Considering the questions are: Does anyone have any official articles about getter-less properties? Or better still - an example of this usage in the .NET Framework itself? and not about opinion; I wrote a quick test application to read through all of the properties of all of the type of all of the assemblies loaded in a default console application:

foreach (var assem in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach (var type in assem.GetTypes())
    {
        foreach (var prop in type.GetProperties())
        {
            if (!prop.CanRead)
                Console.WriteLine("Assembly: {0}; Type: {1}; Property: {2}", assem.FullName, type.Name, prop.Name);
        }
    }
}

The results are:

Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: FileIOAccess; Property: PathDiscovery

Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: RedirectionProxy; Property: ObjectMode

So it looks like the framework uses it sparingly. I'd suggest doing the same.

EDIT

Interestingly enough, running the same code with the debugger attached yields many more results:

Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: FileIOAccess; Property: PathDiscovery
Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: RedirectionProxy; Property: ObjectMode
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: AxHost; Property: Site
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DataGridTextBoxColumn; Property: PropertyDescriptor
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DisplayedBandsData; Property: FirstDisplayedFrozenCol
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DisplayedBandsData; Property: FirstDisplayedFrozenRow
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DisplayedBandsData; Property: LastDisplayedFrozenCol
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DisplayedBandsData; Property: LastDisplayedFrozenRow
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DisplayedBandsData; Property: LastDisplayedScrollingRow
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ErrorProvider; Property: Site
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: WebBrowserBase; Property: Site
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: WebBrowser; Property: Site
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DropDownButton; Property: UseComboBoxTheme
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: GridErrorDlg; Property: Details
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: GridErrorDlg; Property: Message
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DropDownHolder; Property: ResizeUp
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: GridViewEdit; Property: DontFocus
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: GridViewEdit; Property: DisableMouseHook
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: MouseHook; Property: DisableMouseHook
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ManifestSignedXml; Property: Resolver
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ContainerProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: RightToLeftProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: TopDownProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: BottomUpProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ElementProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: VerticalElementProxy; Property: Bounds
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: IconComparer; Property: SortOrder
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: MultiPropertyDescriptorGridEntry; Property: PropertyValue
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ConfigXmlDocument; Property: XmlResolver
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ConfigXmlDocument; Property: InnerText
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ProcessThread; Property: IdealProcessor
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ProcessThread; Property: ProcessorAffinity
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ConfigXmlAttribute; Property: InnerText
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ConfigXmlAttribute; Property: InnerXml
Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: AnonymousPipeServerStream; Property: ReadMode
Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: AnonymousPipeClientStream; Property: ReadMode
Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: ManifestSignedXml; Property: Resolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlResolver; Property: Credentials
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlNullResolver; Property: Credentials
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlSecureResolver; Property: Credentials
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlUrlResolver; Property: Credentials
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlUrlResolver; Property: Proxy
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlUrlResolver; Property: CachePolicy
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlReaderSettings; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlTextReader; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlValidatingReader; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DocumentXmlWriter; Property: NamespaceManager
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DocumentXmlWriter; Property: Navigator
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: DocumentXmlWriter; Property: EndNode
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlAttribute; Property: InnerText
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlAttribute; Property: InnerXml
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlDocument; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlDocument; Property: InnerText
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlUnspecifiedAttribute; Property: InnerText
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlUnspecifiedAttribute; Property: InnerXml
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlPreloadedResolver; Property: Credentials
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XslTransform; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlSchemaSet; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XmlSchemaValidator; Property: XmlResolver
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089; Type: XsdValidator; Property: Context
Community
  • 1
  • 1
M.Babcock
  • 18,753
  • 6
  • 54
  • 84
  • Brilliant answer! I ran this code myself and had the same results. I showed my manager too ;) Thanks! – Connell Mar 02 '12 at 15:51
  • These results don't indicate if the property is publicly accessible or not - which may be an interesting metric. – Sam Axe Mar 02 '12 at 18:51
  • Just because MS does it does not mean that it is a sound practice. – Joshua Drake Mar 02 '12 at 19:54
  • 2
    @JoshuaDrake - I'd agree if we were talking about MS's implementation of C++ or JScript, but when talking about .NET languages (that they designed) there is no better source. – M.Babcock Mar 02 '12 at 20:06
3

I have no official articles or examples.. only opinion.

And in my opinion a property that can not be read from is a beast that will anger and confuse.

It comes down to intent. A property says "I intend to allow the consumer to read me, and possibly even write to me". A function named something like "SetSomeAttribute" declares a write-only intent.

There's also the whole, its my data so why can't I just read it back thing.

So in my opinion, there is never a good reason to use a write-only property.

Sam Axe
  • 33,313
  • 9
  • 55
  • 89
  • The one good example that comes to mind for me is something related to passwords. Maybe there's a credential data structure that allows you to read/write the username, but the password is set-only. It could either have internal methods to compare it to another instance (i.e. the one stored with the correct values) to internally determine if it's valid, or it might have an option to publicly access a hash. – Servy Mar 02 '12 at 18:46
  • @Servy: I'm not sure I follow. If a property is write-only, then NOBODY, not even another instance of that class/struct can read the property. So how would this comparison take place? In this instance I would opt for a .SetPassword() method instead of a write-only property, and compare the public .Hash properties. – Sam Axe Mar 02 '12 at 18:54
  • Well, the code in the setter would obviously need to store it in some private storage medium so that the class could internally access it; and auto-generated property with no getter is obviously pointless (and may even just be removed by the compiler). It would be basically the same as having a setPassword method, I agree. I'm not saying this would be better, merely that this is one rare case where I wouldn't object to it if I saw a team member implement it. – Servy Mar 02 '12 at 19:09
  • Ok. I'll object for the both of us then. :-) – Sam Axe Mar 02 '12 at 19:55
2

There is nothing wrong with having a getter-less property, if that's what makes your code most understandable and maintainable. However, good cases for this are probably extremely rare.

The best thing I have ever used getter-less properties is for unit testing classes whose properties have inaccessible setters. For instance:

public class MyClass
{
    public int MyId { get; protected set; }
}

public class MyClass_Test : MyClass
{
    public int MyId_Set
    {
        set { MyId = value; }
    }
}

This way I can use a MyClass_Test in the unit test and pre-set a value to MyId with the ability to unit test a particular method.

Also, in direct response to your example, using a private get would probably be the better way around it:

public class PropertyWrapper
{       
    public MyClass Property { private get; set; }

    public string FirstProperty
    {
        get { return Property.FirstProperty; } 
    }

    public string SecondProperty
    {
        get { return Property.SecondProperty; } 
    }
}
eouw0o83hf
  • 9,438
  • 5
  • 53
  • 75
  • 1
    Adding some abilities to the class just for unit testing seems to be a code smell. – the_joric Mar 02 '12 at 15:33
  • I guess I could specify that the `_Test` class exists only within the context of the unit test - it allows me to setup the state of the object and test individual methods as single units. – eouw0o83hf Mar 02 '12 at 15:35
  • I am talking about making setter of `MyClass.MyId` protected. If that is going to be used only for unit testing -- that is IMHO not a good design. – the_joric Mar 02 '12 at 15:39
  • I agree - the times that I have had to do this, the `protected set` has been something beyond my control (e.g. external dll) and extending the class was the best option for making unit testing possible. – eouw0o83hf Mar 02 '12 at 15:41
0

Ther's nothing to say you can't do this but it does seem a bit odd. I would have a private property and a method that set it i.e.

    private string _test;

public void SetTest(string test)
{
 _test = test;
}
Liam
  • 27,717
  • 28
  • 128
  • 190
0

You have to remember why properties exist. They replace the following pattern

class Foo
{
     private Bar _bar;

     public Bar GetBar() 
     { 
         return _bar; 
     }

     public void SetBar(Bar bar) 
     { 
         _bar = bar; 
     }
}

While having a property with no Setter looks odd to me, I don't think that having a Set method with no Get method looks odd at all.

In fact, I'm pretty sure that properties are syntactical sugar and get rewritten as get / set methods. Or at least really close.

cadrell0
  • 17,109
  • 5
  • 51
  • 69
  • I am of the same opinion. `SetBar` without a `GetBar` seems normal, but this is what the compiler does to a C# property anyway! – Connell Mar 02 '12 at 15:30
0

They potentially may be used for dependency injection when for some reason neither constructor nor method injection works for you.

But I cannot imagine such case.

the_joric
  • 11,986
  • 6
  • 36
  • 57
0

Well, they can be good use for private fields in object initializers:

var obj = new MyClass
{
    Prop1 = value1,
    Prop2 = value2
};

which could be an alternative way to just write them as constructor parameters:

var obj = new MyClass(value1, value2);

But if there are many (optional) parameters, writeonly properties can be handy.

Balazs Tihanyi
  • 6,659
  • 5
  • 23
  • 24
0

As has already been suggested, I think the answer is whatever is most meaningful in the context of the problem. I can think of two alternatives to having a setter only property, which might be worth trying out to see which is most meaningful:

  1. Define a separate method that accepts a single parameter.
  2. Define a getter that always returns the same value (e.g. null). Whether this makes sense or surprises depends on the context.
Dan Stevens
  • 6,392
  • 10
  • 49
  • 68
-3

You can have properties that are tidier, and over which you can exercise full access control, without the messy _localVariable, and property syntax.

examples :

    /// <summary>
    /// Publicly readable, privately settable.
    /// </summary>
    public int FirstProperty { get; private set; }

    /// <summary>
    /// Entirely private
    /// </summary>
    private int SecondProperty { get; set; }