How to Create a Read-Only Object Property in C#?
As can be seen below, the user is able to change the readonly product field/property:
class Program
{
static void Main(string[] args)
{
var product = Product.Create(开发者_运维百科"Orange");
var order = Order.Create(product);
order.Product.Name = "Banana"; // Main method shouldn't be able to change any property of product!
}
}
public class Order
{
public Order(Product product)
{
this.Product = product;
}
public readonly Product Product;
public static Order Create(Product product)
{
return new Order (product);
}
}
public class Product
{
private Product(){}
public string Name { get; set; }
public static Product Create(string name)
{
return new Product { Name = name };
}
}
I thought it's quite basic but it doesn't seem so.
How to Create a Read-Only Object Property or Field in C#?!
Thanks,
The readonly
keyword prevents you from putting a new instance into the field.
It doesn't magically make any object inside the field immutable.
What do you expect to happen if you write
readonly Product x = Product.Create();
Product y;
y = x;
y.Name = "Changed!";
If you want an immutable object, you need to make the class itself immutable by removing all public setters.
You need to make the Name-property of Product private set:
public class Product
{
private Product(){}
public string Name { get; private set; }
public static Product Create(string name)
{
return new Product { Name = name };
}
}
The problem you are seeing is that you're confusing the readonly
modifier with what you think is a read-only property. The readonly
modifier ensures that the field can only be assigned to through initialisation or a constructor, e.g here are valid uses of readonly
:
public class MyClass
{
private readonly int age = 27; // Valid, initialisation.
}
public class MyClass
{
private readonly int age;
public MyClass()
{
age = 27; // Valid, construction.
}
}
public class MyClass
{
private readonly int age;
public int Age { get { return age; } set { age = value; } } // Invalid, it's a readonly field.
}
What you are finding, is that your Person
class itself is mutable, this means although the field Order.Product
is readonly, the internal structure of Person
is not. To this end, if you want to make a readonly property, you'll likely want to create your type as immutable - being that its internal structure/values cannot change.
Readonly fields can always be modified in the constructor (as you're doing). If you try to edit the field elsewhere, you should get a compiler error.
the readonly keyword means the field can only be set in the constructor & the value cannot be changed.
If the value is a reference type, it doesn't make the object read-only
If you want to make the property readonly, just omit the setter.
To better demonstrate the properties of readonly
:
order.Product = Product.Create("Apple") // <- not allowed because Product field is readonly
order.Product.Name = "Apple" // <- allowed because Name is not readonly field or private property
After making setter of property Name private (e.g. public string Name { get; private set; }
):
order.Product.Name = "Apple" // <- not allowed
The problem with this solution, of course is:
new Product { Name = "Orange" } // <- not allowed if called from outside Product class
You have 2 options:
- if you don't want any properties of Product be modifiable (e.g. be immutable) then simply don't define setters (or make setters private) and have constructor (or factory method) of Product initialize them
- if you still want properties of Product be modifiable but not when it's a member of Order then create an interface IProduct with property Name defined as:
public string Name { get; }
, make Product implement this interface, and then make Product field of type Order defined as:public readonly IProduct Product;
Any of this solutions will achieve both of your requirements:
order.Product.Name = "Apple" // <- not allowed
new Product { Name = "Orange" } // <- allowed when called from Product's Create method
With only difference between them:
order.Product.Name = "Apple" // <- not allowed
Product product = new Product { Name = "Orange" } // not allowed in solution #1 when called from outside Product, allowed in solution #2
product.Name = "Apple" // <- not allowed in solution #1 but allowed in solution #2
精彩评论