How to use the keyword 'with' on an interface without knowing the underlying record type?
Asked Answered
P

1

11

In C#, is there a way of using the with keyword on an interface, without knowing the type (or base type) of the object?

I'm imagining something like the DoSomething method in the following pseudo-code (which is not valid c#). The code DoSomething would be expected to act on all records that implement ISomeRecord.

The DoSomething2 method would compile, but work only in records of type SomeRecord and not of records of types other than SomeRecord.

public interface ISomeRecord
{
  bool SomeProp { get; }
}

public record SomeRecord : ISomeRecord 
{ 
  public bool SomeProp { get; init; } 
}

public class SomeUtil
{
  public ISomeRecord DoSomething(ISomeRecord rec)
  {
    return ( rec as record ) with { SomeProp = false };
  }
  public ISomeRecord DoSomething2(ISomeRecord rec)
  {
    return ( rec as SomeRecord ) with { SomeProp = false };
  }
  public ISomeRecord DoSomething3<T>(T rec) where T : struct, ISomeRecord
  {
    return rec with { SomeProp = false };
  }
}

As an aside, in C# 10 (in preview at moment of writing) I notice that DoSomething3 compiles. C# 10 introduces record structs and record classes. The latter is the default. It appears what I'm after is possible for record struct objects, but not for record class objects. In other words, in the example,DoSomething3 can't be called with a SomeRecord as an argument unless SomeRecord is changed to a record struct.

I don't see a way to do the same with a record class which is what I need in my use case.

Portcullis answered 15/8, 2021 at 23:58 Comment(4)
I wouldn't expect DoSomething to compile, as an interface isn't a record. You could add an abstract record you inherit from that implements the interface, but I don't think that's what you're looking for as you'll still have to use the abstract record type and not the interface for your SomeUtil methods.Keheley
Hi @Zer0, Yes. It's the general case that I'm after. That is the case where we want to apply the DoSomething logic to any record that implements ISomeRecord. IE not restiricted to those inheriting from the SomeRecord base class.Portcullis
Yeah I don't think that's possible. But maybe someone smarter than me has an answer. You also can't where with as a generic constraint AFAIK, so not a lot of options if you want this to be at interface level.Keheley
My guess is that reflection is the best way to accomplish this. Records all have copy constructors and reflection can get around the readonly access restrictions.Sidman
T
0

This doesn't look to be currently possible in .NET 8 (C# 11).

By definition the with syntax is only valid on structs and records that are value based and not on classes which reference based. "with" is supposed to be quick way of cloning the value of an instance, If you want to do the same thing with classes you have to use the IClonable interface and manage explicitly/manually how the clone instance is created.

Because of this distinction using with on an interface would lead to ambiguity and this might explain why they decided to omitted it's support on interfaces, since technically an interface can represent both at the same thing.

I expand on the comment of @Zero by adding an example of how to achieve the closest thing with an abstract class:

public abstract record SomeRecordBase
{
   public abstract bool SomeProp { get; init; }
}

public record SomeRecord : SomeRecordBase
{
    public override bool SomeProp { get; init; }
}

// as an extension method

public static class SomeUtilExtension
{
    public static SomeRecordBase DoSomething(this SomeRecordBase rec)
    {
        return rec with { SomeProp = false };
    }
}

// as a utility class
public class SomeUtil
{
    public SomeRecordBase DoSomething(SomeRecordBase rec)
    {
        return rec with { SomeProp = false };
    }
}

//TESTER CODE
public static class MainProgram
{
    public static void Main()
    {
        SomeRecord rec = new SomeRecord { SomeProp = true };
        SomeRecordBase recBase = rec.DoSomething();
    }
}
Trichinize answered 10/9 at 15:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.