There are two arguments why you'd rather not expose public fields:
- This limits future evolution of your design, assuming you need to offer a stable API.
- This tends to lead to higher coupling between components, in particular to more complex data flows between components. And complexity leads to bugs.
Let's first discuss API stability.
There are two different kinds of codebases: one kind contains applications and in-house code. Everything is under your control and can be refactored freely. The other kind is a library with external consumers. Now you need to think about API stability, because you can't just change stuff. Code that worked with the old API should continue to work with the new API.
In applications, API stability doesn't matter because you can always refactor anything, and all dependent code along with it. Much of the OOP architecture advice you might read doesn't apply to applications.
Libraries are more tricky. Your first public design should be close to the ideal design, or at least allow opportunity for future evolution in a backwards-compatible manner. When we have a public member variable, we commit to there always being this variable. The variable will also accept any value when set, except as restricted by its type.
Properties and accessor methods are much more flexible. For example, we can remove the variable and re-compute it on the fly. Or we can add validation code. Or we can trigger events when a variable is updated. Since properties and accessor methods allow us to supply arbitrary code, this is much more flexible. In C#, a class that is part of a public API should almost always use auto-properties in place of public fields due to this future-compatibility aspect.
Another nice aspect in C# is that properties can be part of interfaces. This may also help with creating loosely coupled interfaces between components. This works because properties are just a different syntax for special methods.
The other aspect I mentioned is that using methods can lead to simpler data flows and less coupling between components.
One reason is that methods/properties let us separate the public API from our private implementation. E.g. we might not want to have other components assign to a field directly. In that case, a private set
property might be very useful.
But allowing unrestricted getting/setting might not be a particularly good API for our object. An object should not be a bunch of named fields (whether as plain fields, properties, or getters/setters), but should provide some capabilities to its users. We should be able to tell an object to just do a thing, not ask the object for all data necessary so we can do the thing ourselves (see Tell-Don't-Ask by Martin Fowler).