Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

218
Visualizações
Ignoring specific fields when using "with" on a C# 9 record?

When creating a new instance of a C# 9 record by using the with keyword, I'd like to ignore some fields instead of copying them into the new instance too.

In the following example, I have a Hash property. Because it is very expensive in computation, it is only computed when needed and then cached (I have a deeply immutable record, so that hash will never change for an instance).

public record MyRecord {

   // All truely immutable properties
   public int ThisAndManyMoreComplicatedProperties { get; init; } 
   // ...

   // Compute only when required, but then cache it
   public string Hash {
      get {
         if (hash == null)
            hash = ComputeHash();
         return hash;
      }
   }

   private string? hash = null;
}

When calling

MyRecord myRecord = ...;
var changedRecord = myRecord with { AnyProp = ... };

changedRecord contains the hash value from myRecord, but what I want to have is the default value null again.

Any chance to mark the hash field as "transient"/"internal"/"reallyprivate"..., or do I have to write my own copy-constructor to mimic this feature?

over 4 years ago · Santiago Trujillo
5 Respostas
Responde à pergunta

0

If I'm understanding you correctly you want to create a new MyRecord object with some of the properties of an existing MyRecord object?

I think something along these lines should work:

MyRecord myRecord = ...;
var changedRecord = new MyRecord with { AnyProp = myRecord.AnyProp... };
over 4 years ago · Santiago Trujillo Relatório

0

As you can see with sharplab.io decompilation the with call is translated into <Clone>$() method call which internally calls copy constructor generated by compiler, so you need to define your own copy constructor to prevent Hash being called.

Also as the with keyword doc states:

If you need to customize the record copy semantics, explicitly declare a copy constructor with the desired behavior.

over 4 years ago · Santiago Trujillo Relatório

0

I think the only built-in mechanism to allow this is the "copy constructor". As documented in this post:

A record implicitly defines a protected “copy constructor” – a constructor that takes an existing record object and copies it field by field to the new one...

The "copy constructor" is just a constructor that receives an instance of the same type of the record as an argument. If you just implement this constructor you can override the default behavior of the with expression. I've made a test based on your code, here's the record declaration:

public record MyRecord
{
    protected MyRecord(MyRecord original)
    {
        ThisAndMayMoreComplicatedProperties = original.ThisAndMayMoreComplicatedProperties;
        hash = null;
    }

    public int ThisAndMayMoreComplicatedProperties { get; init; }

    string? hash = null;
    public string Hash
    {
        get
        {
            if (hash is null)
            {
                Console.WriteLine("The stored hash is currently null.");
            }
            return hash ??= ComputeHash();
        }
    }

    string ComputeHash() => "".PadLeft(100, 'A');
}

Notice that when I call the property getter I check if the hash is null and print a message. Then I've made a little program to check:

var record = new MyRecord { ThisAndMayMoreComplicatedProperties = 100 };
Console.WriteLine($"{record.Hash}");

var newRecord = record with { ThisAndMayMoreComplicatedProperties = 200 };
Console.WriteLine($"{newRecord.Hash}");

If you run this you notice that both calls to Hash will print the message that the private hash is null. If you comment the copy constructor you'll see that only the first call prints the null.

So I think this solves your problem. The downside of this method is that you have to manually copy every property of your record, which can be pretty annoying. If you record has lots of properties you can use Reflection to iterate over then and copy only the ones you want. You may also define a custom Attribute to mark ignoring fields. But bear in my mind that using Reflection always have processing overhead.

over 4 years ago · Santiago Trujillo Relatório

0

I found a workaround for my problem. This does not solve the general problem, and it has another disadvantage: I have to cache the last state of the object, until the hash was recomputed. I understand this is a tradeoff between a potentially heavy computation and higher memory usage.

The trick is to remember the last object reference when the hash was computed. When calling the Hash property again, I check if meanwhile the object reference has been changed (i.e. if a new object was created).

public string Hash {
   get {
      if (hash == null || false == ReferenceEquals(this, hashRef)) {
         hash = ComputeHash();
         hashRef = this;
      }
      return hash;
   }
}
private string? hash = null;
private MyRecord? hashRef = null;

I'm still looking for a better solution.

EDIT: I recommend Heinzi's solution!

over 4 years ago · Santiago Trujillo Relatório

0

I found a workaround: You can (ab)use inheritance to split the copy constructor in two parts: A manual one only for hash (in the base class) and an auto-generated one in the derived class copying all your valuable data fields.

This has the additional advantage of abstracting away your hash (non-)caching logic. Here's a minimal example (fiddle):

abstract record HashableRecord
{
    protected string hash;
    protected abstract string CalculateHash();
    
    public string Hash 
    {
        get
        {
            if (hash == null)
            {
                hash = CalculateHash(); // do expensive stuff here
                Console.WriteLine($"Calculating hash {hash}");
            }
            return hash;
        }
    }
    
    // Empty copy constructor, because we explicitly *don't* want
    // to copy hash.
    public HashableRecord(HashableRecord other) { }
}

record Data : HashableRecord
{
    public string Value1 { get; init; }
    public string Value2 { get; init; }

    protected override string CalculateHash() 
        => hash = Value1 + Value2; // do expensive stuff here
}

public static void Main()
{
    var a = new Data { Value1 = "A", Value2 = "A" };
    
    // outputs:
    // Calculating hash AA
    // AA
    Console.WriteLine(a.Hash);

    var b = a with { Value2 = "B" };
    
    // outputs:
    // AA
    // Calculating hash AB
    // AB
    Console.WriteLine(a.Hash);
    Console.WriteLine(b.Hash);
}
over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda