Uma maneira simples de conseguir isso seria ter uma interface que permita ler propriedades e chamar apenas métodos somente leitura e uma classe que implemente essa interface, que também permite que você escreva essa classe.
Seu método que o cria, lida com o primeiro e, em seguida, retorna o último, fornecendo apenas uma interface somente leitura para interagir. Isso não requer cópia e permite que você ajuste facilmente os comportamentos que deseja que estejam disponíveis para o chamador, em vez do criador.
Veja este exemplo:
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;
}
}
A única desvantagem dessa abordagem é que alguém poderia simplesmente convertê-la na classe de implementação. Se fosse uma questão de segurança, simplesmente usar essa abordagem é insuficiente. Uma solução alternativa para isso é que você pode criar uma classe de fachada para agrupar a classe mutável, que simplesmente apresenta uma interface com a qual o chamador trabalha e não pode ter acesso ao objeto interno.
Dessa forma, nem mesmo o elenco o ajudará. Ambos podem derivar da mesma interface somente leitura, mas converter o objeto retornado fornecerá apenas a classe Facade, que é imutável, pois não altera o estado subjacente da classe mutável empacotada.
Vale ressaltar que isso não segue a tendência típica na qual um objeto imutável é construído de uma vez por todas através de seu construtor. Compreensivelmente, você pode estar tendo que lidar com muitos parâmetros, mas deve se perguntar se todos esses parâmetros precisam ser definidos antecipadamente ou se alguns podem ser introduzidos posteriormente. Nesse caso, um construtor simples com apenas os parâmetros necessários deve ser usado. Em outras palavras, não use esse padrão se estiver encobrindo outro problema no seu programa.