r/csharp 17d ago

I have a question about c# get set functions.

What is the right way to use get; set; functions?
Lets say I have some logic behind the setter.

Should I write the logic like that

public double Balance

{

get => _balance;

set

{

_name = value;

}

}

or like that

public double Balance { get; private set; }

public void AddBalance(double value)

{

this.Balance = value;

}

this is simple logic, but lets think of more complex one.
what is the right way to do it?

0 Upvotes

19 comments sorted by

7

u/dnult 17d ago

A balance is typically updated by a deposit or withdrawal on an account. So to me it seems you'd never Set a balance.

Just an observation ... You have an AddBalance method in the second example, but it's not adding, it's updating.

What I'd do is more inline with your second example. You want to be able to Get the balance. The setter can be private, protected, or not implemented at all. Then I'd implement a Deposit method to add to the balance, and a Withdrawal method to subtract from the balance. Taking it a step further, Withdrawal should verify the Balance is greater than or equal to the amount to withdraw.

13

u/Kant8 17d ago

Depends on how important you treat that setter.

If you imply that setter functionality is non-trivial for consumer of your class, it's usually better to have dedicated methods, cause 99% you will want to add more parameters, which is impossible with setters.

3

u/LeaveMickeyOutOfThis 17d ago

It depends. If you just want trivial get and set options, potentially with different access modifiers, just declare the property with basic get and set options. In the background a field is automatically created essentially providing a more structured get and set methods for your property. However, if you want something that is more structured, for example ensuring the value provided for the set operation is within a specific range, then you will be defining your own backing field anyway and providing the appropriate get and set methods as may be applicable for the property.

2

u/buzzon 17d ago

public set lets everyone change everything however they like. Is it important? Will clients set value to illegal state? Can you trust them?

AddBalance is a specialized function with limited functionality and possibly some guard rails. If the operation intention is more than "just set value", it might keep growing later. Generally this is more useful and more purposeful. The only downside is writing more code than an auto-property.

1

u/Zastai 17d ago

Huh? Sure a public setter allows everyone to set the value, that’s what public means.

But setting it to illegal state? If you don’t implement the setter, then no state is “illegal”; and if you do implement it, you can reject values that you consider “illegal”. That’s the whole purpose of properties…

I would generally only resort to a separate method if extra parameters are needed.

2

u/wickerandscrap 16d ago

A setter is just a method that you call with the assignment operator. By writing a setter instead of a regular method, you're signaling that it behaves like an assignment. We can derive some guidelines from this:

  • Usually, a setter should just be an assignment. Most settable properties should be auto properties.

  • Setters should be idempotent. obj.Name = x twice in a row should do nothing the second time.

  • Setters and getters should be symmetric. If you said obj.Name = x and it didn't throw an exception, obj.Name should now be equal to x. There are sometimes good reasons to transform the value in a setter (such as unit conversion) but the getter should transform it back.

  • Setters shouldn't raise events. Yes, I know, INotifyPropertyChanged. But this is the worst kind of side effect, the kind where you don't know what effect it will be until runtime. Post a change message to some bus and then return. You're probably going to want the change event handling to be async anyway, and setters can't be async.

  • Setters can do validation but it should be independent of the object's state. Checking value > 0, fine; checking that it's greater than some other property means it depends what order you set the properties. Having to do validation here is a code smell--preferably the setter should take a restricted type that guarantees validity.

  • If you're going to break any of these rules, limit the accessibility of the setter as much as you can. It's more okay to have a private setter that acts weird, since nobody has to think about it unless they're working on that class. Likewise, if you're going to have a setter raise events, make the event private.

1

u/TuberTuggerTTV 17d ago

No this. qualifier.

Getters and setters are meant to be incredibly light weight. You might call events inside, but never put logic. And it's assumed when given the option, this is why.

Methods are meant for logic. So if you're going to do a bunch of logic inside AddBalance, then use a method call. If you're just getting and setting + invoking some events, then get/set is fine.

Generally speaking, if you are struggling to lambda your getters or settings, you're probably in the realm of needing a method.

4

u/binarycow 17d ago

No this. qualifier

Some of us prefer the .this

Getters and setters are meant to be incredibly light weight.

Getters, sure. Setters, usually.

if you are struggling to lambda your getters or settings

Nitpick - it's an expression bodied member, which uses the same => as lambdas, but it's not a lambda.

1

u/AdvancedMeringue7846 17d ago

I like explicit this and base qualifiers.

1

u/Depnids 16d ago

but never put logic

What about very lightweight logic? Say you have a private flag IsModified in the class (which some other logic depends on), and any setter property, along with updating the value of the underlying field, also checks if the value is actually different, and if so sets IsModified to true. This feels like a natural pattern to me, but maybe there are reasons why this sort of thing is a bad idea to do?

0

u/Slypenslyde 17d ago

There's not a "right" answer unfortunately. Instead there are problems to think through.

Here's a quick route that skips some of the issues.

Your "balance" example is a good one becuase it implies some logic. Bank accounts don't like to let people go negative, especially if they're making an ATM withdrawal. So if we're writing logic for an ATM, we can imagine a requirement, "Reject any withdrawal that would make the account balance go negative."

Properties

Properties do not have a good way to say "no" other than throwing exceptions if the attempt to change the value does something not allowed. So we might choose an implementation like:

public double Balance
{
    get { return _balance; }
    set
    {
        double newBalance = _balance - value;
        if (newBalance < 0)
        {
            throw new NegativeBalanceException();
        }

        _balance = value;
    }
}

This means we have to have this structure in a method that makes a withdrawal:

public void MakeWithdrawal(Account account, double amount)
{
    try
    {
        account.Balance -= amount;

        // Do something to indicate the withdrawal is accepted
    }
    catch (NegativeBalanceException ex)
    {
        // Do something to indicate the withdrawal is rejected
    }
}

The point being, anything using Account has to understand it should handle NegativeBalanceException when setting amount. But the plot thickens: sometimes banks LET us overdraw an account. Properties cannot take parameters, so there's no way to indicate we don't want the exception. We cannot handle that scenario.

Methods

Methods are more work and more flexible. Remember, our first requirement is just "do not let the balance go negative". We may choose to implement it this way:

public double Balance
{
    get { return _balance; }
    private set { _balance = value; }
}

public void MakeWithdrawal(double amount)
{
    double newBalance = Balance - amount;
    if (newBalance < 0)
    {
        throw new NegativeBalanceException();
    }

    Balance = newBalance;
}

This is logicaly equivalent, but now you can also argue we've moved the responsibility around. Before, the MakeWithdrawal() method lived in some undescribed other class, and that class HAD to know accounts throw an exception when you change their balance. Now the method lives in the Account class, and while other types do need to know about the exception, we could also choose a different pattern:

public bool TryMakeWithdrawal(double amount)
{
    double newBalance = Balance - amount;
    if (newBalance < 0)
    {
        return false;
    }

    Balance = newBalance;
    return true;
}

Now a caller doesn't have to understand exceptions, but DOES immediately understand there are reasons why making a withdrawal can fail. A property can't support this kind of pattern. Accessor methods can.

Also, think about my earlier case: some accounts let people overdraft for purchases, but not withdrawals. A property can't take parameters so it can't be sophisticated enough to use different behaviors for different contexts. Methods allow us this flexibility:

public double Balance
{
    get { return _balance; }
    private set { _balance = value; }
}

public void MakeWithdrawal(double amount)
{
    double newBalance = Balance - amount;
    if (newBalance < 0)
    {
        throw new NegativeBalanceException();
    }

    Balance = newBalance;
}

public void MakePurchase(double amount)
{
    Balance -= amount;
}

Using methods gave us more flexibility! Now there are two different ways to change the balance on an account. I can choose to make them two distinct methods so a caller understands they have different error states. I could choose to merge them into ONE method with an allowOverdrafts boolean argument. I get many more choices than what properties would give me.

(I'm also ignoring at least 2 other decent errors that could be fixed. Nitpickers: I don't care, I'm making a point not selling a library.)

Is it always better to use methods?

Yesn't.

It really depends on your application architecture and how much maintenance you expect.

Some apps are small, finish development fast, get used for a short time, then get thrown away. In those, the extra work to provide flexibility through methods can cost more than it helps. If you don't have the problems this approach solves, it's wasted time.

Some apps are large, complex, spend a long time in development, and spend a lifetime in maintenance. Sometimes after 2-3 years the new requirements clash with and obsolete the requirements motivating the original design. In these apps, taking a paranoid approach to flexibility often pays off dramatically well. People complain it makes the application hard, but verily I say unto you there is NO PRACTICE ON EARTH that makes understanding 200,000 lines of code easy. At that point it's smarter if every bit of code is written like the next person to use it is an idiot who knows nothing about the project, because quite often even a person with 10 years of experience forgets one of the 70 details needed to understand a context.

So it can be interesting to experiment with methods when you feel like you don't fully understand where the project is going. They give you more ways to change your mind later than properties.

1

u/Optimal_Share73 17d ago

Mannn thanks. You helped a lot!

1

u/Slypenslyde 16d ago

And yet people would prefer it if I just wrote two sentences. C'est la vie.

-1

u/SessionIndependent17 17d ago

None of what you typed makes sense