Encapsulation means that the state of the object occurs only through a certain interface, and because of this, the class can make sure that this state is always correct and corresponds to the purpose of the class.
Thus, in some cases, it is fully consistent with the principle of encapsulation in order to publicly publish the field - all possible values for the field are valid with all other possible values of all other fields, and therefore the programmer can actively decide to freely manipulate the field with external code.
These cases, although mostly limited to classes, which are mostly “plain old data”. They are also not very interesting in this regard, so much about them.
In other cases in other languages, you have the getter and setter method, something like int getId() to get the value and void setId(int val) to update it.
Properties allow you to use the same syntax for reading and writing using methods that we will use to read and write fields. It is a good syntactic sugar, although not vital.
(Actually, because of how reflection works, and in cases like DataBinder.Eval , it may be convenient to have a property, even if the field works fine, but that's another matter).
Until private setters are introduced (in fact, what has changed from C # 2 is the syntax for having a private setter and a public or protected getter in the same block), we could have a private method for working a private setter therefore private setters are really not needed. They are convenient though, although while just syntactic sugar, they are very useful.
Encapsulation is not a question of whether your setters (or getters) are public, private, protected, or internal, but the question of whether they are suitable. Start with the default for each field that is private (and for that matter, readonly ), and then add members (properties or methods) that change these fields if necessary, and make sure that the object remains valid when changed . This ensures that the invariant of the class is preserved, which means that the rules that describe the valid set of states in which it may be are never interrupted (constructors also help by making sure that it starts in this correct state).
As for your last question, to be immutable, it means that the class does not have public, protected or internal setters and there are no public, protected or internal methods that change any fields. There are degrees, in C # three degrees are possible:
The entire field of the readonly class instance, so even private code cannot change it. It is guaranteed to be unchanged (everything that tries to change it will not compile), and, possibly, optimization can be performed on the reverse side.
The class is immutable from the outside, because none of the public members changes anything, but does not guarantee the use of readonly , so as not to change from the inside.
The class is immutable, as can be seen from the outside, although some state changes as part of the implementation. For example. the field can be seen, and therefore, from outside the attempt to get it just gets the same value, the first such attempt actually calculates it, and then saves it for retrieval in subsequent attempts.