A simple way to achieve this would be to have an interface that allows one to read properties and call only read only methods and a class that implements that interface which also lets you write that class.
Your method that creates it, deals with the former, and then returns the latter providing only a read only interface to interact with. This would require no copying and it allows you to easily fine-tune behaviors you want available to the caller as opposed to the creator.
Take this example:
public interface IPerson
{
public String FirstName
{
get;
}
public String LastName
{
get;
}
}
public class PersonImpl : IPerson
{
private String firstName, lastName;
public String FirstName
{
get { return firstName; }
set { firstName = value; }
}
public String LastName
{
get { return lastName; }
set { lastName = value; }
}
}
class Factory
{
public IPerson MakePerson()
{
PersonImpl person = new PersonImpl();
person.FirstName = 'Joe';
person.LastName = 'Schmoe';
return person;
}
}
The only disadvantage to this approach is that one could simply cast it to the implementing class. If it were a matter of security, then simply using this approach is insufficient. A workaround for this is that you can create a facade class to wrap the mutable class, which simply presents an interface that the caller works with and cannot have access to the internal object.
In this way, not even casting will help you. Both can derive from the same read only interface, but casting the returned object will only give you the Facade class, which is immutable as it doesn't change the underlying state of the wrapped mutable class.
It's worth mentioning that this doesn't follow the typical trend in which an immutable object is constructed once and for all through its constructor. Understandably, you might be having to deal with many parameters, but you should ask yourself if all these parameters need to be defined up front or if some can be introduced later. In that case, a simple constructor with only required parameters should be used. In other words, don't use this pattern if it is covering up another problem in your program.