C# Corner

Choose Between Methods and Properties

Language features aren't good or bad. The choice of language feature depends on what you're doing. In this issue, we dive into the methods vs. properties debate.

TECHNOLOGY TOOLBOX: C#

A quick Web search on any number of language features will turn up numerous blogs and forum posts espousing that some feature of some programming language is pure evil and should be avoided at all costs. I might agree in the case of GOTO, but just about every other feature of every modern programming language has some use. For example, I've been getting some questions recently about whether properties are a bad idea, and whether developers should use methods instead. The short answer is as clear as mud: Sometimes you should use properties, and sometimes you should use methods. The longer answer is that you must live up to your client developers' expectations. The developers that use your class will view a method call differently than they'll view a property access. You can see the fact that properties and methods are the same because you're the one building the class, but your users see the world differently. Properties look like data and the developers that use your class will expect those properties to behave like data. This column will explore those expectations, and show you how to best determine when you should implement functionality as a property or as a method.The most obvious expectation is that users will believe the observable state for your object won't change simply because they've accessed a property. Retrieving a value from an object shouldn't make any changes to the object. Property getters should have no reason to change an object's state. You'd never write this property:

// Obvious bad example.
private int numberofAccesses;
public int NumberOfAccesses
{
    get
    {
        return ++numberofAccesses;
    }
}

But real problems like this are much more subtle. Consider writing this property with some extra code to keep track of the number of accesses:

// this seems ok.
private int numberofAccesses;

private int someValue;
public int SomeValue
{
    get
    {
        numberofAccesses++;
        return someValue;
    }
}

Assume the enclosing class should utilize value-based semantics. You'd probably add code like this:

public class BadPropertiesSample : 
    IEquatable<BadPropertiesSample>
{
    private int numberofAccesses;

    private int someValue;
    public int SomeValue
    {
        get
        {
            numberofAccesses++;
            return someValue;
        }
    }
    
	#region IEquatable<BadPropertiesSample> Members
    public bool Equals(BadPropertiesSample other)
    {
        return (other.someValue == someValue) && 
            (other.numberofAccesses == numberofAccesses);
    }
    #endregion
}

This breaks a user's expectations. What do you suppose this code fragment produces for output?

BadPropertiesSample s1 = new BadPropertiesSample();
BadPropertiesSample s2 = new BadPropertiesSample();

Console.WriteLine(s1.Equals(s2));
Console.WriteLine(s1.SomeValue);
Console.WriteLine(s1.Equals(s2));

This code writes "true", "0", and then "false". Examining a property changes the type's concept of equality, which breaks a user's expectation for the type. Somehow, examining a property changes the object's state in a suprising manner. That's counterintuitive, and it's going to cause client developers lots of headaches. In fact, this points to one of the few design flaws in the .NET Framework.

DateTime.Now violates similar principles. Repeated calls to the same property, without intervening calls, should return the same value. In the case of DateTime.Now, that's not true. The return from DateTime.Now changes with time, not with any explicit code changes to some static property of DateTime.

Client developers always hold the expectation that calling a property getter won't change the object's state. Repeated calls to a getter will always return the same value. You shouldn't do anything to violate those expections.

Performance Expectations
Developers using your type will also have expectations about the performance of properties. Property getters shouldn't perform lengthy operations to calculate the return value. Consider a simple loop around an array:

for (int index = 0; index < numbers.Length; index++)

Internally, the Length property is stored in the Array object; it returns the cached value. But, as an alternative, assume the Length property was implemented by counting the number of values in the array. That would make the Length property performance change linearly with the length of the array. In this loop, the Length property is called once for every element in the array. That means the Length property is called N times for an N length array, so the loop would have quadratic performance (N x N) if the Length property were implemented by counting the values in the array. That's going to be such a problem that users would never use that feature. It's not implemented that way in the .NET Framework. The Length property is implemented in such a way that it has constant, usually quick performance.

You could run into this problem if your property accessors do too much work. This could be local code that performs lengthy calculations to compute a property value. Or worse, you could implement property get and set accessors to load or store data from a remote data store, such as a database. That would cause your client developers even greater headaches. Retrieving a value would access network resources, access a database, retrieve values across the wire, and then provide you with the answer. Think about how you and your peers use properties: You would never think that a property accessor did that much work. It's counterintuitive. The same is true for setters. You probably assume that some validation takes place when you update a property value, but you probably don't expect network access, possible concurrency exceptions, or network access exceptions. Again, the problem is that you've violated your client developers' expectations. They'd believe that a property accessor, either for get or set, would be a quick, local action. Instead, you've performed a mountain of work underneath what looks like a simple act.

Consider Alternatives
Please don't take these examples as a global recommendation against properties, or even against executing code in property accessors. Rather, it's a strong recommendation to consider methods over properties when a property's implementation would lead a developer to get results different from what he would normally expect. You can and should consider lazy evaluation for property getters. You should add appropriate validation to your set accessors. It's when that validation, or lazy evaluation, becomes unwieldy that you violate your client developers' expectations.

Think about all the counter examples I gave earlier. Any of those would make reasonable methods. Developers using your class would expect that a method called "SaveChangesToDatabase()" would require network calls and might have concurrency issues. Setting MyType.SomeValue doesn't carry that same expectation. Developers using your code will write different access code for the method and the property. The same assumptions are true for a method named "LoadRecordCountFromDatabase()" and a property named MyCollection.RowCount. The first implies the performance metrics associated with a network call and database access. The second doesn't.

Note that the change from a property to a well-formed method name also implies different behavior. I mentioned in the opening that users expect repeated calls to the same property getter will always return the same value, and should not change any state. Your expectations for a method called "LoadRowCountFromDatabase()" carries different expectations. You expect that repeated calls might return different values. For example, other users might have added or removed rows. You would also expect that it might fail if the network were unavailable. The same analogy exists for the save methods. Developers using your code will consider many different ways that a save method to a database might fail. There could be concurrency failures, network failures, or validation errors. Properties don't carry those expectations.

Properties and Method members both have proper places in your class design. Just remember that your client developers will have different expectations for each, and your job as an API designer is to ensure that the implementation lives up to those expectations.

About the Author

Bill Wagner, author of Effective C#, has been a commercial software developer for the past 20 years. He is a Microsoft Regional Director and a Visual C# MVP. His interests include the C# language, the .NET Framework and software design. Reach Bill at [email protected].

comments powered by Disqus

Featured

  • Hands On: New VS Code Insiders Build Creates Web Page from Image in Seconds

    New Vision support with GitHub Copilot in the latest Visual Studio Code Insiders build takes a user-supplied mockup image and creates a web page from it in seconds, handling all the HTML and CSS.

  • Naive Bayes Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the naive Bayes regression technique, where the goal is to predict a single numeric value. Compared to other machine learning regression techniques, naive Bayes regression is usually less accurate, but is simple, easy to implement and customize, works on both large and small datasets, is highly interpretable, and doesn't require tuning any hyperparameters.

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

Subscribe on YouTube

Upcoming Training Events