2

I'm trying to make it to where I can't access the record properties based on their name but rather though an extension method.

For instance, I want to be able to do this Axe.GetById(1378).GetName()
But not be able to do Axe.GetById(1378).Name

I tried declaring a private field inside the record, but then I can't access it in my extension method at all in order to return it.

public sealed record AxeEnum(string Name, int LevelReq, int ItemId)
{
    private int privateField;
}
static class Axe
{
    static Dictionary<int, AxeEnum> AXES = new Dictionary<int, AxeEnum>();
    static AxeEnum STEEL = new AxeEnum("Steel", 41, 1378);


    static Axe()
    {
        AXES.Add(GetId(STEEL), STEEL);
    }

    public static AxeEnum GetById(int id)
    {
        AXES.TryGetValue(id, out var axe);
        return axe;
    }

    public static int GetLvlReq(this AxeEnum axe) => axe.LevelReq;
    public static int GetId(this AxeEnum axe) => axe.ItemId;
    public static string GetName(this AxeEnum axe) => axe.Name;
}
Jess Chan
  • 391
  • 2
  • 14
  • 2
    Is there a particular reason why you want extension methods instead of get-only properties? I don't understand the motivation behind not allowing access to `Axe.GetById(1378).Name`, yet still wanting to access it somehow. It kinda sounds like a XY problem to me... –  Oct 13 '22 at 09:20
  • 1
    Extension methods look like they are part of the type when you call them but they're not, so they only have access to the same public interface that all other code does. What you're asking for is not possible. – jmcilhinney Oct 13 '22 at 09:21
  • It was mainly because I prefer to expose properties through functions due to a background of Java :') – Jess Chan Oct 13 '22 at 10:07

2 Answers2

2

where I can't access the record properties based on their name

If you don't want the shape that records present: don't use records; just create your own class with whatever members and accessibility rules you want.


I tried declaring a private field inside the record, but then I can't access it in my extension method at all in order to return it.

Extension methods must still respect accessibility rules; at the moment the field is private, which is inaccessible except by code in that type, or nested types, and extension methods must be on top-level (non-nested) types. We can't make use of protected, since extension methods must be on static types (which can't inherit anything), plus the record here is sealed. So: that leaves internal:

public sealed record AxeEnum(string Name, int LevelReq, int ItemId)
{
    internal int privateField;
}

Personally, though: I'd probably just make this a public instance property or method on the record itself.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
2

It's sort-of possible to do this if you make AxeEnum a class instead of a record and declare it nested inside the Axe class.

You also have to make a private nested interface for the properties you want to access from within the class.

It would look something like this:

static class Axe
{
    // Private interface for the properties that this class needs to access.
    interface IAxeEnumPrivates
    {
        string Name     { get; }
        int    LevelReq { get; }
        int    ItemId   { get; }
    }

    // Nested class with explicit implementation of the properties.
    public sealed class AxeEnum : IAxeEnumPrivates
    {
        public AxeEnum(string name, int levelReq, int itemId)
        {
            _name     = name;
            _levelReq = levelReq;
            _itemId   = itemId;
        }

        string IAxeEnumPrivates.Name  => _name;
        int IAxeEnumPrivates.LevelReq => _levelReq;
        int IAxeEnumPrivates.ItemId   => _itemId;

        readonly string _name;
        readonly int    _levelReq;
        readonly int    _itemId;
    }

    static Dictionary<int, AxeEnum> AXES  = new Dictionary<int, AxeEnum>();
    static AxeEnum STEEL = new AxeEnum("Steel", 41, 1378);

    static Axe()
    {
        AXES.Add(GetId(STEEL), STEEL);
    }

    public static AxeEnum? GetById(int id)
    {
        AXES.TryGetValue(id, out var axe);
        return axe;
    }

    public static void DoSomethingWithAxe(AxeEnum axe)
    {
        // Need to assign the concrete class to a variable of the interface type in order
        // to access its properties.

        IAxeEnumPrivates iAxe = axe;

        // Now you can do things with the properties:

        Console.WriteLine(iAxe.Name);
        Console.WriteLine(iAxe.LevelReq);
        Console.WriteLine(iAxe.ItemId);

        // But you can't access the properties via the concrete class:

        // Console.WriteLine(axe.Name); // Won't compile!
    }

    // We must implement these by casting the concrete AxeEnum class
    // to the private interface, so that we can access the properties.
    // This is completely safe because AxeEnum implements the interface.

    public static int GetLvlReq(this AxeEnum axe) => ((IAxeEnumPrivates)axe).LevelReq;
    public static int GetId(this AxeEnum axe) => ((IAxeEnumPrivates)axe).ItemId;
    public static string GetName(this AxeEnum axe) => ((IAxeEnumPrivates)axe).Name;
}

However this looks like a fiddly solution to an X-Y problem...

Also see this related answer from Eric Lippert

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276