-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add user-facing docs for Variant #38712
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,135 @@ | ||||||
# Azure.Variant | ||||||
|
||||||
`Azure.Variant` is an implementation of a [tagged union](https://en.wikipedia.org/wiki/Tagged_union). It can hold both reference and value types and can be used to avoid boxing .NET primitives. The list of value types that Variant can hold without boxing is below. | ||||||
|
||||||
<details> | ||||||
<summary>Value types that `Variant` won't box</summary> | ||||||
|
||||||
- `byte` | ||||||
- `byte?` | ||||||
- `sbyte` | ||||||
- `sbyte?` | ||||||
- `bool` | ||||||
- `bool?` | ||||||
- `char` | ||||||
- `char?` | ||||||
- `short` | ||||||
- `short?` | ||||||
- `int` | ||||||
- `int?` | ||||||
- `long` | ||||||
- `long?` | ||||||
- `ushort` | ||||||
- `ushort?` | ||||||
- `uint` | ||||||
- `uint?` | ||||||
- `ulong` | ||||||
- `ulong?` | ||||||
- `float` | ||||||
- `float?` | ||||||
- `double` | ||||||
- `double?` | ||||||
- `DateTimeOffset` | ||||||
- `DateTimeOffset?` | ||||||
- `DateTime` | ||||||
- `DateTime?` | ||||||
- Enums | ||||||
|
||||||
</details> | ||||||
|
||||||
In principle, `Variant` is similar to `object`, but without boxing as described above. However, since it's not `object`, there are different APIs needed to achieve some of the same functionality, including: | ||||||
scottaddie marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
- [Assigning a value to `Variant`](#assign-a-value-to-variant) | ||||||
- [Retrieving the value a `Variant` holds](#get-the-value-from-variant) | ||||||
- [Working with `null` and `Variant`](#handling-nulls) | ||||||
|
||||||
## Assign a value to Variant | ||||||
|
||||||
`Variant` has implicit cast operators for each of the primitives listed above. This means you can assign a value of those types to `Variant` without casting, and a new `Variant` instance holding an unboxed copy of the value will be created. | ||||||
|
||||||
```csharp | ||||||
Variant v = 3L; | ||||||
|
||||||
// v.Type is System.Int64 | ||||||
``` | ||||||
|
||||||
Since you can't have an implicit cast operator from `object`, for reference types, you must create a new instance of `Variant` and pass the value to the `Variant` constructor. | ||||||
scottaddie marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
```csharp | ||||||
MemoryStream s = new(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to include a
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good callout. I don't want to add complexity to the samples, so I'll pick a type for the examples that isn't disposable to sidestep the issue. Thanks, @scottaddie! |
||||||
Variant v = new(s); | ||||||
|
||||||
// v.Type is System.IO.MemoryStream | ||||||
``` | ||||||
|
||||||
`Variant` will box value types that aren't on the list above, because it only stores 16 bytes and user-defined value types might exceed this size. The one exception is for `enum`, which `Variant` can hold without boxing. Because we can't add implicit cast operators for unknown enum types, you must call the `Create` method to create a `Variant` that holds an enum without boxing. | ||||||
scottaddie marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
```csharp | ||||||
Value v = Value.Create(System.Color.Blue); | ||||||
|
||||||
// v.Type is System.Color | ||||||
``` | ||||||
|
||||||
## Get the value from Variant | ||||||
|
||||||
`Variant` has explicit cast operators for each of the primitives listed above, as well as to `string`. That means you can assign a `Variant` holding one of these types to a variable of that type with a cast: | ||||||
|
||||||
```csharp | ||||||
Variant v = 3; | ||||||
int i = (int)v; | ||||||
``` | ||||||
|
||||||
If you don't know the type of the value that a `Variant` is holding, you can read that from the `Variant.Type` property. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```csharp | ||||||
switch (v.Type) | ||||||
{ | ||||||
case Type s when s == typeof(string): | ||||||
Console.WriteLine($"string: {v}"); | ||||||
break; | ||||||
case Type i when i == typeof(int): | ||||||
Console.WriteLine($"int: {(int)v}"); | ||||||
break; | ||||||
} | ||||||
``` | ||||||
|
||||||
If you do know the type the `Variant` is holding, you can use the `As<T>` method to get its value as that type. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a potential caller, I'm not clear on whether the explicit cast operation, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh. You do explain that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good questions - let me clarify this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```csharp | ||||||
MemoryStream s = new(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Observation: Our team is very inconsistent with how we instantiate in our code samples. Here, the target-typed new expression is used. In other locations, this same line of code may have been written in one of the following forms:
This would be a good topic for the next .NET team sync. /cc: @jsquire There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it was already codified a long time ago in our root Though, we never have discussed the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My personal preference is for not using |
||||||
Variant v = new (s); | ||||||
|
||||||
MemoryStream streamValue = v.As<MemoryStream>(); | ||||||
``` | ||||||
|
||||||
If you call `Variant.As<T>` and the `Variant` isn't holding the type that you ask for, it will throw an `InvalidCastException`. To try to retrieve the value without risking an exception being thrown, you can use the `TryGetValue` method instead. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```csharp | ||||||
if (v.TryGetValue(out string s)) | ||||||
{ | ||||||
Console.WriteLine($"string: {s}"); | ||||||
} | ||||||
|
||||||
if (v.TryGetValue(out int i)) | ||||||
{ | ||||||
Console.WriteLine($"int: {i}"); | ||||||
} | ||||||
``` | ||||||
|
||||||
## Handling nulls | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid gerunds in headings.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the thinking behind this rule? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I won't die on this hill, but there are 3 reasons:
|
||||||
|
||||||
`Variant` handles nullable primitives without boxing them for value types on the supported list above. It also supports holding a reference type with a `null` value. In either of these cases, the variant's `Type` property will return `null`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way I read this, any nullable value type will have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, good. Let me clarify this. IIRC, the Type property is only null for a nullable value type when the value of the Nullable is null. Before I checked, I had originally written that for value types we would return the actual type from the Type property (e.g. What do you think? Would that be a better API experience? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think it would be much better to return the actual nullable type, if possible. The alternatives both seem worse (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking to COM's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree with this. Let me look at a) how hard it is to implement and b) what the implications are for type switching. I think in cases where we come in through a specific constructor (e.g. I'm happy to look at examples where this gets interesting though, so we can think about whether a design choice like this might paint us into a corner. What are the pro/con cases here? |
||||||
|
||||||
Working with nulls with Variant can be a little tricky for the following reasons. First, since a Variant instance holding `null` has a value (i.e., it's a Variant holding a `null`), any comparison to `null` will return false. Second, assigning a `Variant` to an instance that holds `null` could require you to call the `Variant` constructor and cast the `null` some type to avoid ambiguous method resolution errors. Because of this, there are some APIs that make working with `null` and `Variant` a little easier. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grammar hound time 😄 : should you put "(i.e., it's a Variant holding a
Suggested change
Alternatively, remove "i.e.," and just have it in parens. More of a question, though. I've never seen both used this way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to fix - I always have to fix up my grammar over multiple drafts 🙂 |
||||||
|
||||||
To check whether a Variant holds `null`, you can read the `IsNull` property: | ||||||
|
||||||
```csharp | ||||||
bool isNull = v.IsNull; | ||||||
``` | ||||||
|
||||||
To assign the value a Variant holds to `null`, you can set it to `Variant.Null`: | ||||||
|
||||||
```csharp | ||||||
Variant v = Variant.IsNull; | ||||||
Comment on lines
+131
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You document There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! Will fix - thanks! |
||||||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Omit the locale from the URL. Also, avoid using directional terms like "below".