-
-
Save heaths/d105148428fe09a2631322b656f04ebb to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?> | |
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> | |
<CodeSnippet Format="1.0.0"> | |
<Header> | |
<Title>Enumeration-style Structure</Title> | |
<Keywords> | |
<Keyword>enum</Keyword> | |
</Keywords> | |
<Shortcut>enumStruct</Shortcut> | |
</Header> | |
<Snippet> | |
<Code Language="CSharp"><![CDATA[ | |
public readonly struct $typeName$ : IEquatable<$typeName$> | |
{ | |
private readonly string _value; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="$typeName$"/> structure. | |
/// </summary> | |
/// <param name="value">The string value of the instance.</param> | |
public $typeName$(string value) | |
{ | |
_value = value ?? throw new ArgumentNullException(nameof(value)); | |
} | |
// TODO: Define internal or private string constants here if you want to use them in switch statements: | |
// internal const string s_value1 = "value1"; | |
// TODO: Define well-known values here (use constant values from above if defined): | |
// public static $typeName$ Value1 { get; } = new $typeName$(s_value1); | |
// public static $typeName$ Value2 { get; } = new $typeName$("value2"); | |
/// <summary> | |
/// Determines if two <see cref="$typeName$"/> values are the same. | |
/// </summary> | |
/// <param name="left">The first <see cref="$typeName$"/> to compare.</param> | |
/// <param name="right">The second <see cref="$typeName$"/> to compare.</param> | |
/// <returns>True if <paramref name="left"/> and <paramref name="right"/> are the same; otherwise, false.</returns> | |
public static bool operator ==($typeName$ left, $typeName$ right) => left.Equals(right); | |
/// <summary> | |
/// Determines if two <see cref="$typeName$"/> values are different. | |
/// </summary> | |
/// <param name="left">The first <see cref="$typeName$"/> to compare.</param> | |
/// <param name="right">The second <see cref="$typeName$"/> to compare.</param> | |
/// <returns>True if <paramref name="left"/> and <paramref name="right"/> are different; otherwise, false.</returns> | |
public static bool operator !=($typeName$ left, $typeName$ right) => !left.Equals(right); | |
/// <summary> | |
/// Converts a string to a <see cref="$typeName$"/>. | |
/// </summary> | |
/// <param name="value">The string value to convert.</param> | |
public static implicit operator $typeName$(string value) => new $typeName$(value); | |
/// <inheritdoc/> | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
public override bool Equals(object obj) => obj is $typeName$ other && Equals(other); | |
/// <inheritdoc/> | |
public bool Equals($typeName$ other) => string.Equals(_value, other._value, StringComparison.Ordinal); | |
/// <inheritdoc/> | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
public override int GetHashCode() => _value?.GetHashCode() ?? 0; | |
/// <inheritdoc/> | |
public override string ToString() => _value; | |
// TODO: If you need to attach other data to the struct value, follow the pattern below: | |
// internal int GetNumberValue() => _value switch | |
// { | |
// s_value1 => 1, | |
// _ => 0, | |
// }; | |
} | |
]]> | |
</Code> | |
<Declarations> | |
<Literal Editable="true"> | |
<ID>typeName</ID> | |
<ToolTip>The name of the structure you want to define.</ToolTip> | |
</Literal> | |
</Declarations> | |
<Imports> | |
<Import> | |
<Namespace>System</Namespace> | |
</Import> | |
<Import> | |
<Namespace>System.ComponentModel</Namespace> | |
</Import> | |
</Imports> | |
</Snippet> | |
</CodeSnippet> | |
</CodeSnippets> |
Done. For some structures, we need to attach extra information. Prior, these were implemented as extension method on traditional enums rather than duplicating code. Now that we define enum-like structs, it makes sense to bake those methods into the struct without additional allocation like I initially did with KeyCurveName
(and will migrate to this to reduce stack allocation). Most enum-like structs won't need it, but why define such logic where it is needed separately?
I'm worried that people would put too much stuff onto the structs making them heavy or put some information that needs to be roundtripped along with the string etc.
That's why these are only methods that act on the sole data field. The current KeyCurveName
declares other fields that do increase the stack size for allocating the struct, but I plan on migrating to this other style that was filled before by extension methods on enums (i.e. in both cases no data was actually added to the value type).
Ah, I see what you are saying. I guess it's fine as long you need these values in multiple places.
Is there any guidance on how this can be used in scenarios where we would otherwise be using a Flags enum, i.e. there could be multiple values?
Currently, no. It came up once, but we never came to resolution since we didn't actually know of any need at the time. One thing we considered as adding an |
operator that would concatenate however appropriate (e.g. "," or "+" separated). Would love feedback or any input on this otherwise.
I guess I wouldn't need this for any string concatenation, but just as a repository for the different values I am setting. A HasFlag method similar to Flags enum would be nice.
But that really only makes sense for a flags-enum, which is the only case in which you'd define a OR/concatenation operator; otherwise, it's just a comparison you already get with the equality operator. Do you have another example in mind?
Yeah, I just meant that it didn't matter how the string of values was represented, just as long as I could get which flags were set.
This would be for a permissions enum-style struct.
It's all connected, though. If you expect that the "enum" might be modified often and contain many keys, storing the value as a concatenation vs. array of values that we format when needed may be better in some cases or other. It's certainly something we can add to the definition and example (perhaps as another example for clarity).
A concatenation of values (perhaps through a StringBuilder
might be better since a HasFlags
implementation could use IndexOf
to see if a value is set, vs. loop through an array doing string comparisons. Last I checked, IndexOf
was more efficient - but probably not by much.
Or a HashSet?
Typically the overhead of bucketing isn't worth it until you have sufficient number of elements. In fact, there are types in .NET that use one implementation or another as private implementation depending on a threshold.
public struct $typeName$ : IEquatable<$typeName$>
add readonly
I would try not to encourage people to add additional info and keep these structs "pure".