C
C#LordKalma (CT7ALW)

Smart enums, inheritance and "siblings" behaving weirdly and I don't understand why.

So, I'm using Ardalis.SmartEnum to create some smart enums with data members. I found myself having a lot of boilerplate, so I came up with the following code:
using Ardalis.SmartEnum;

namespace AetherLogger.Models;

public interface IModeInterface : ISmartEnum
{
string ReadableName { get; }
}

public abstract class ModeBase<TEnum> : SmartEnum<TEnum, int>, IModeInterface where TEnum : SmartEnum<TEnum, int>
{
protected ModeBase(string name, string? readableName = null) : base(name, ++_counter)
{
ReadableName = readableName ?? name;
}

private static int _counter;
public string ReadableName { get; }
}

public sealed class Mode : ModeBase<Mode>
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);

public IEnumerable<IModeInterface>? Submodes { get; }

private Mode(string name, string? readableName = null, IEnumerable<IModeInterface>? submodes = null) : base(name, readableName)
{
Submodes = submodes;
}

public Mode(string name, IEnumerable<IModeInterface> submodes) : this(name, name, submodes)
{
}
}


public sealed class ChipSubMode(string name, string? readableName = null) : ModeBase<ChipSubMode>(name, readableName)
{
public static readonly ChipSubMode CHIP64 = new(nameof(CHIP64), "CHIP 64");
public static readonly ChipSubMode CHIP128 = new(nameof(CHIP128), "CHIP 128");
}

public sealed class CwSubMode(string name, string? readableName = null) : ModeBase<CwSubMode>(name, readableName)
{
public static readonly CwSubMode PCW = new(nameof(PCW), "Precision CW");
}
using Ardalis.SmartEnum;

namespace AetherLogger.Models;

public interface IModeInterface : ISmartEnum
{
string ReadableName { get; }
}

public abstract class ModeBase<TEnum> : SmartEnum<TEnum, int>, IModeInterface where TEnum : SmartEnum<TEnum, int>
{
protected ModeBase(string name, string? readableName = null) : base(name, ++_counter)
{
ReadableName = readableName ?? name;
}

private static int _counter;
public string ReadableName { get; }
}

public sealed class Mode : ModeBase<Mode>
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);

public IEnumerable<IModeInterface>? Submodes { get; }

private Mode(string name, string? readableName = null, IEnumerable<IModeInterface>? submodes = null) : base(name, readableName)
{
Submodes = submodes;
}

public Mode(string name, IEnumerable<IModeInterface> submodes) : this(name, name, submodes)
{
}
}


public sealed class ChipSubMode(string name, string? readableName = null) : ModeBase<ChipSubMode>(name, readableName)
{
public static readonly ChipSubMode CHIP64 = new(nameof(CHIP64), "CHIP 64");
public static readonly ChipSubMode CHIP128 = new(nameof(CHIP128), "CHIP 128");
}

public sealed class CwSubMode(string name, string? readableName = null) : ModeBase<CwSubMode>(name, readableName)
{
public static readonly CwSubMode PCW = new(nameof(PCW), "Precision CW");
}
This code works. However, note ModeBase<TEnum> : SmartEnum<TEnum, int>. What I found is that this code doesn't work if I don't template ModelBase and I don't know why. [continues in the comments]
LC
LordKalma (CT7ALW)159d ago
This does not work:
using Ardalis.SmartEnum;

namespace AetherLogger.Models;

public interface IModeInterface : ISmartEnum
{
string ReadableName { get; }
}

public abstract class ModeBase : SmartEnum<ModeBase>, IModeInterface
{
protected ModeBase(string name, string? readableName = null) : base(name, ++_counter)
{
ReadableName = readableName ?? name;
}

private static int _counter;
public string ReadableName { get; }
}

public sealed class Mode : ModeBase
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);

public IEnumerable<IModeInterface>? Submodes { get; }

private Mode(string name, string? readableName = null, IEnumerable<IModeInterface>? submodes = null) : base(name, readableName)
{
Submodes = submodes;
}

public Mode(string name, IEnumerable<IModeInterface> submodes) : this(name, name, submodes)
{
}
}


public sealed class ChipSubMode(string name, string? readableName = null) : ModeBase(name, readableName)
{
public static readonly ChipSubMode CHIP64 = new(nameof(CHIP64), "CHIP 64");
public static readonly ChipSubMode CHIP128 = new(nameof(CHIP128), "CHIP 128");
}

public sealed class CwSubMode(string name, string? readableName = null) : ModeBase(name, readableName)
{
public static readonly CwSubMode PCW = new(nameof(PCW), "Precision CW");
}
using Ardalis.SmartEnum;

namespace AetherLogger.Models;

public interface IModeInterface : ISmartEnum
{
string ReadableName { get; }
}

public abstract class ModeBase : SmartEnum<ModeBase>, IModeInterface
{
protected ModeBase(string name, string? readableName = null) : base(name, ++_counter)
{
ReadableName = readableName ?? name;
}

private static int _counter;
public string ReadableName { get; }
}

public sealed class Mode : ModeBase
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);

public IEnumerable<IModeInterface>? Submodes { get; }

private Mode(string name, string? readableName = null, IEnumerable<IModeInterface>? submodes = null) : base(name, readableName)
{
Submodes = submodes;
}

public Mode(string name, IEnumerable<IModeInterface> submodes) : this(name, name, submodes)
{
}
}


public sealed class ChipSubMode(string name, string? readableName = null) : ModeBase(name, readableName)
{
public static readonly ChipSubMode CHIP64 = new(nameof(CHIP64), "CHIP 64");
public static readonly ChipSubMode CHIP128 = new(nameof(CHIP128), "CHIP 128");
}

public sealed class CwSubMode(string name, string? readableName = null) : ModeBase(name, readableName)
{
public static readonly CwSubMode PCW = new(nameof(PCW), "Precision CW");
}
(note that now we have ModeBase : SmartEnum<ModeBase>, IModeInterface)
LC
LordKalma (CT7ALW)159d ago
Even Visual Studio shows me a weird hint that ChipSubMode. and CwSubMode. are not needed before List.
No description
LC
LordKalma (CT7ALW)159d ago
Doing this the following test is completly broken:
[TestFixture]
public class ModeTests
{
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes?.First().Name;
var pcw = Mode.CW.Submodes?.First().Name;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.EqualTo("CHIP64"));
Assert.That(pcw, Is.EqualTo("PCW"));
});
}
}
[TestFixture]
public class ModeTests
{
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes?.First().Name;
var pcw = Mode.CW.Submodes?.First().Name;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.EqualTo("CHIP64"));
Assert.That(pcw, Is.EqualTo("PCW"));
});
}
}
With the error:
System.TypeInitializationException : The type initializer for 'AetherLogger.Models.Mode' threw an exception. ----> System.NullReferenceException : Object reference not set to an instance of an object.
And I don't know which bit of the language I don't understand that causes this error. I would like to learn. Thank you.
Message:  System.TypeInitializationException : The type initializer for 'AetherLogger.Models.Mode' threw an exception. ----> System.NullReferenceException : Object reference not set to an instance of an object. Stack Trace:  ModeTests.DifferentModesHaveDifferentSubModes() line 9 RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) --NullReferenceException <>c.<GetAllOptions>b4_2(TEnum t) EnumerableSorter2.ComputeKeys(TElement[] elements, Int32 count) EnumerableSorter1.ComputeMap(TElement[] elements, Int32 count) EnumerableSorter1.Sort(TElement[] elements, Int32 count) OrderedEnumerable1.Fill(Buffer1 buffer, Span1 destination) OrderedEnumerable1.ToArray() SmartEnum2.GetAllOptions() Lazy1.ViaFactory(LazyThreadSafetyMode mode) Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) Lazy`1.CreateValue() <.cctor>b36_0() Lazy1.ViaFactory(LazyThreadSafetyMode mode) Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) Lazy1.CreateValue() SmartEnum2.get_List() Mode.cctor() line 23
With
public sealed class Mode : ModeBase
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
// public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);
public sealed class Mode : ModeBase
{
public static readonly Mode CHIP = new(nameof(CHIP), ChipSubMode.List);
// public static readonly Mode CW = new(nameof(CW), "Morse (CW)", CwSubMode.List);
and
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes?.First().Name;
// var pcw = Mode.CW.Submodes?.First().Name;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.EqualTo("CHIP64"));
// Assert.That(pcw, Is.EqualTo("PCW"));
});
}
}
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes?.First().Name;
// var pcw = Mode.CW.Submodes?.First().Name;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.EqualTo("CHIP64"));
// Assert.That(pcw, Is.EqualTo("PCW"));
});
}
}
The test still fails
Message:  System.TypeInitializationException : The type initializer for 'AetherLogger.Models.Mode' threw an exception. ----> System.NullReferenceException : Object reference not set to an instance of an object. Stack Trace:  ModeTests.DifferentModesHaveDifferentSubModes() line 9 RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) --NullReferenceException <>c.<GetAllOptions>b4_2(TEnum t) EnumerableSorter2.ComputeKeys(TElement[] elements, Int32 count) EnumerableSorter1.ComputeMap(TElement[] elements, Int32 count) EnumerableSorter1.Sort(TElement[] elements, Int32 count) OrderedEnumerable1.Fill(Buffer1 buffer, Span1 destination) OrderedEnumerable1.ToArray() SmartEnum2.GetAllOptions() Lazy1.ViaFactory(LazyThreadSafetyMode mode) Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) Lazy`1.CreateValue() <.cctor>b36_0() Lazy1.ViaFactory(LazyThreadSafetyMode mode) Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) Lazy1.CreateValue() SmartEnum2.get_List() Mode.cctor() line 23
I also thought that probably a single member it should work, but apparently not?
CHIP = new(nameof(CHIP), null);
CHIP = new(nameof(CHIP), null);
and
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.Null);
});
}
[Test]
public void DifferentModesHaveDifferentSubModes()
{
var chip64 = Mode.CHIP.Submodes;
Assert.Multiple(() =>
{
Assert.That(chip64, Is.Null);
});
}
Does solve it, yeah Thanks for the deep dive! Wow! What amazes me the most after all of this is that the static analyser picked up that the .List` would be the same This looks like it makes some use of reflection, so having all of that in the static analyser is kind of amazing
M
MODiX159d ago
Zeth
REPL Result: Success
class L<T> { public static int K; }

L<int>.K = 3;

(L<int>.K, L<string>.K)
class L<T> { public static int K; }

L<int>.K = 3;

(L<int>.K, L<string>.K)
Result: ValueTuple<int, int>
{
"item1": 3,
"item2": 0
}
{
"item1": 3,
"item2": 0
}
Compile: 394.234ms | Execution: 39.682ms | React with ❌ to remove this embed.
LC
LordKalma (CT7ALW)159d ago
ah because List is a static member and that is shared when they aren't generic, got it VS recommended to just put List in there, no prefix yes that, sorry yes yes, that's why I'm saying, it was just "hey, this symbol is available from here" but yeah, seems that Ardalis.SmartEnum is a smartely crafted library pun totally intended thanks for the time for the deep dive!
Want results from more Discord servers?
Add your server
More Posts
Hey guys I need some troubleshooting with a C# task using BFSI have a task that I's mostly working but sometimes I have undesirable outputs and not behaving righMVVM - View model with collection of view models?Within MVVM, can a view model have a collection of view models? Is it okay to do: ``` class BasketV2 QuestionsHi, I need to ask 2 questions as i am new to C#. So im missing 2 tasks to deploy my first app. And iRaw SQL Query QuestionI have a raw SQL query that I need to execute against a database that's external to my application. ✅ Help for unityGood evening I am looking for someone who can teach me how to create a game from A to Z. Even if I dAny way to get XAML Auto-complete in VS Code?I recognize this isn't quite a C# question, but it's related. I want to make cross platform apps usi.Net 8 Blazor Web App Identity Framework HelpSuper quick overview: I have a Blazor Web App that is single process only hosting UI. It communicaRegex Group Containshey simple easy question, but are there ways to access individual capture groups in a regex match? I"netsh" cmd Commands not being executedWell, I am trying to set up some firewall-rules and such, the problem is non of the commands is beinAutomate the process of transferring a Microsoft SQL Server .bak file to MongoDBI'm looking to automate the process of transferring a Microsoft SQL Server backup file (e.g., bulk.bMapping controller to / without breaking other controllersHi there, I would like to rename `HomeController` to `DefaultController` and instead of going to `/helpwhy is doesn't workTrack Thread/Task Progress through SignalR/WebSocketsI have an ASP.NET Web Core API and an Angular project both connected. In the dashboard of my Angularazor pages vs .cshtmlTeam , What is the difference between razor pages and cshtml.? razor pages looks like old asp.net w✅ mobile applicationMy question is quite fundamental, the way I imagine mobile apps is that they utilize an API to conneIt's not showing the database in datagridviewI'm having trouble trying to show the database that the teacher gave me and the demo she gave me to Can't seem to make a universal filepathHi there could anyone explain how I can make a universal filepath so other pc's can locate the fileshey! looking for fellow C# noobs!Does anyone want to vc and group study,go over things etc? No dumb questions or rudeness I'm alsl beStuttering in UI and I don't know whyEverytime I launch the app, the very first time I navigate to any FlyoutItem I get this stuttering iIssue with Filtering in DataGridI use Telerik UI's DataGrid control for .Net Maui. As you see VIPStatus column holds values such as