Skip to content

Instantly share code, notes, and snippets.

@heaths
Last active April 8, 2021 05:19
Show Gist options
  • Save heaths/d105148428fe09a2631322b656f04ebb to your computer and use it in GitHub Desktop.
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>
@pakrym
Copy link

pakrym commented Sep 18, 2019

public struct $typeName$ : IEquatable<$typeName$>

add readonly

 // 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,
    };

I would try not to encourage people to add additional info and keep these structs "pure".

@heaths
Copy link
Author

heaths commented Sep 18, 2019

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?

@pakrym
Copy link

pakrym commented Sep 18, 2019

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.

@heaths
Copy link
Author

heaths commented Sep 18, 2019

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).

@pakrym
Copy link

pakrym commented Sep 18, 2019

Ah, I see what you are saying. I guess it's fine as long you need these values in multiple places.

@JoshLove-msft
Copy link

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?

@heaths
Copy link
Author

heaths commented Sep 30, 2019

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.

@JoshLove-msft
Copy link

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.

@heaths
Copy link
Author

heaths commented Sep 30, 2019

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?

@JoshLove-msft
Copy link

JoshLove-msft commented Sep 30, 2019

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.

@heaths
Copy link
Author

heaths commented Sep 30, 2019

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).

@heaths
Copy link
Author

heaths commented Sep 30, 2019

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.

@JoshLove-msft
Copy link

Or a HashSet?

@heaths
Copy link
Author

heaths commented Sep 30, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment