Contracts在 Visual Studio 2022 的設計期間,有沒有什么方法可以檢查變數的值是否符合某些規則?
即int在 C# 中被大量使用,但我們真正想要的是uint,也就是說只有正值,標準是檢查是否傳遞負值以引發錯誤/例外/等。
但是是否有某種方法可以撰寫一個方法并告訴 Visual Studio 或編譯器或任何“此引數必須> = 0”,并且每當開發人員傳遞一個小于 0 的值時,Visual Studio 串列中就會顯示一個錯誤錯誤?
uj5u.com熱心網友回復:
通常,代碼合同會非常適合這一點 - 但似乎該功能在微軟從 .NET Framework 到 .NET Core 的大躍進中令人沮喪地死去并被遺棄,這意味著需要一些替代方法。
雖然使用 Roslyn 代碼分析基本上重新實作代碼契約是完全可能的,但這不是一件小事,并且可能需要您花費數月時間來構建一個分析器,該分析器可以在其生命周期內可證明地驗證變數的可能運行時邊界(即,從數學上講,一般情況下無法解決)。
因此,另一種選擇是使用細化型別/謂詞型別/依賴型別 (我忘記了它們之間的確切區別) -但總體要點是您使用新的不同型別來表示對其包含值的約束。由于 C# 是靜態型別的,這意味著可以使用型別來表示運行時狀態不變數。
在 C# 中,這些型別通常實作為不可變readonly struct型別,因為(通常)開銷為零。您還可以將其與運算子多載、IEquatable<T>擴展方法、范圍out引數和implicit轉換相結合,以提供非常符合人體工程學的細化型體驗。
(這是我真正同情 Java 用戶的地方,因為 Java 沒有值型別,也沒有運算子多載,也沒有擴展方法,也沒有用戶定義的隱式轉換 - 哎喲)。
注意:在定義implicit轉換時,只定義從(窄)精煉型別到更廣泛型別的隱式轉換(因為這總是會成功)是非常重要的 - 并且你絕不能定義從更廣泛型別到受限型別的隱式轉換型別,因為如果更廣泛的值無效,那么當您的驗證代碼抱怨時將導致運行時例外,編譯器將無法接受。
所以在你的情況下,你想要一個型別來表示一個正的非零Int32值 - 所以你想要這樣的東西:
(此代碼省略了實作VS 喜歡抱怨的struct/樣板 - 但它包含在下面的版本中)。IEquatable<>*.snippet
public static class PositiveInt32Extensions
{
public static Boolean IsPositive( this Int32 candidate, out PositiveInt32 value ) => PositiveInt32.TryCreate( candidate, out value );
}
public readonly struct PositiveInt32
{
public static Boolean TryCreate( Int32 candidate, out PositiveInt32 value )
{
if( candidate > 0 )
{
value = new PositiveInt32( candidate );
return true;
}
else
{
value = default;
return false;
}
}
private readonly Int32 value;
public PositiveInt32( Int32 value )
{
if( value < 1 ) throw new ArgumentOutOfRangeException( nameof(value), actualValue: value, message: "Value must be positive." );
this.value = value;
}
public static implicit operator Int32( PositiveInt32 self ) => self.value;
// NOTE: This implicit conversion will fail when `unsignedValue > UInt32.MaxValue / 2`, but I assume that will never happen.
public static implicit operator PositiveInt32 ( UInt32 unsignedValue ) => new PositiveInt32( (Int32)unsignedValue );
}
這是我個人 *.snippet對 Visual Studio 的改進型別——我希望它對你有用:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<!--
Usage/installation instructions:
1. Save to a file `refine.snippet` somewhere (e.g. in `C:\Users\You\Documents\Visual Studio {year}\Code Snippets\Visual C#\My Code Snippets`).
* If saved outside your `Visual Studio {year}` folder, or if it isn't detected, add it manually via <kbd>Tools > Code Snippets Manager...</kbd> (Tip: ensure the top "Language" drop-down says "CSharp" as it defaults to ASP.NET for some reason).
2. To try it out, open a .cs file and move your cursor/caret to inside a `namespace`, then type the word "`refine`" and IntelliSense should list it as a snippet in the completion-list popup.
* If it doesn't appear despite being recognized by Code Snippets Manager ensure VS is configured to list Snippets in the code completion list (Tools > Options > Text Editor > C# > IntelliSense > Snippets behavior > "Always include snippets").
3. Press <kbd>Tab</kbd> once or twice (it varies...) and it should be inserted, with the caret moved to the first `$refinementname$` placeholder. Type the new value then press <kbd>Tab</kbd> to move to the $supertypename$ placeholder. Press <kbd>Tab</kbd> or <kbd>Enter</kbd> when done.
-->
<CodeSnippet Format="1.0.0">
<Header>
<Title>refine</Title>
<Shortcut>refine</Shortcut>
<SnippetTypes>
<!-- There are only 2 types of Snippets: "Expansion" and "Surround-With", btw: https://docs.microsoft.com/en-us/visualstudio/ide/code-snippets?view=vs-2022 -->
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
<Description>Refinment type represented by a public readonly struct with implicit conversion support.</Description>
</Header>
<Snippet>
<Declarations>
<Object Editable="true">
<ID>refinementname</ID>
<Type>Object</Type>
<ToolTip>PascalCased summary of the refinement type's predicate - this is concatenated with $supertype$ for the final struct name. e.g. "ValidatedEmailAddress" ("ValidatedEmailAddressUser")</ToolTip>
<Default>NewRefinement</Default>
</Object>
<Object Editable="true">
<ID>supertype</ID>
<Type>Object</Type>
<ToolTip>The name of the type that is being refined. e.g. "User" (for "ValidatedEmailAddressUser")</ToolTip>
<Default>SupertypeName</Default>
</Object>
</Declarations>
<!-- Inside <Code>, the only reserved-token names are `$end$` and `$selected$`. Both can only be used at-most once. -->
<!-- BTW, for this snippet specifically, should the `.Value` property's getter self-validating? or always trust the constructor instead? What's the best way to prevent `default(StructType)` thesedays? -->
<Code Language="CSharp" Kind="type decl"><![CDATA[
public static partial class RefinementExtensions
{
public static Boolean Is$refinementname$$supertype$( this $supertype$ value, [NotNullWhen(true)] out $refinementname$$supertype$? valid )
{
return $refinementname$$supertype$.TryCreate( value, out valid );
}
/// <summary>Throws <see cref="ArgumentException"/> if <paramref name="value"/> does not satisfy the refinement predicate.</summary>
/// <exception cref="ArgumentException"></exception>
public static $refinementname$$supertype$ To$refinementname$$supertype$( this $supertype$ value )
{
return $refinementname$$supertype$.Create( value );
}
}
public readonly struct $refinementname$$supertype$ : IReadOnly$supertype$, IEquatable<$refinementname$$supertype$>, IEquatable<$supertype$>
{
#region Create / TryCreate
/// <summary>Throws <see cref="ArgumentException"/> if <paramref name="value"/> does not satisfy the refinement predicate.</summary>
/// <exception cref="ArgumentException"></exception>
public static $refinementname$$supertype$ Create( $supertype$ value )
{
if( TryCreate( value, out $refinementname$$supertype$? valid ) ) return valid.Value;
else throw new ArgumentException( paramName: nameof(value), message: "Argument object does not satisfy " nameof($refinementname$$supertype$) "'s refinement predicate." );
}
/// <summary>Returns <see langword="null"/> if <paramref name="value"/> does not satisfy the refinement predicate.</summary>
public static $refinementname$$supertype$? TryCreate( $supertype$ value )
{
return TryCreate( value, out $refinementname$$supertype$? valid ) ? valid : null;
}
/// <summary>Returns <see langword="false"/> if <paramref name="value"/> does not satisfy the refinement predicate.</summary>
public static Boolean TryCreate( $supertype$ value, [NotNullWhen(true)] out $refinementname$$supertype$? valid )
{
if( CONDITION )
{
valid = new $refinementname$$supertype$( value );
return true;
}
return false;
}
#endregion
public static implicit operator $supertype$( $refinementname$$supertype$ self )
{
return self.Value;
}
private $refinementname$$supertype$( $supertype$ value )
{
this.value_doNotReadThisFieldExceptViaProperty = value ?? throw new ArgumentNullException(nameof(value));
}
private readonly $supertype$ value_doNotReadThisFieldExceptViaProperty;
public $supertype$ Value => this.value_doNotReadThisFieldExceptViaProperty ?? throw new InvalidOperationException( "This " nameof($refinementname$$supertype$) " value is invalid." );
public override String ToString() => this.Value.ToString();
#region IReadOnly$supertype$
// TODO?
#endregion
#region IEquatable<$refinementname$$supertype$>, IEquatable<$supertype$>
private Boolean IsDefault => this.value_doNotReadThisFieldExceptViaProperty is null;
public override Boolean Equals( Object? obj )
{
if( this.IsDefault )
{
return obj is null;
}
else if( obj is $supertype$ super )
{
return this.Equals( super: super );
}
else if( obj is $refinementname$$supertype$ other )
{
return this.Equals( other: other );
}
else
{
return false;
}
}
public Boolean Equals( $refinementname$$supertype$ other )
{
return ( this.IsDefault && other.IsDefault ) || ( this.Value == other.Value );
}
public Boolean Equals( $supertype$? super )
{
return !this.IsDefault && ( this.Value == super );
}
public override Int32 GetHashCode()
{
if( this.IsDefault ) return 0;
return this.Value.GetHashCode(); // return HashCode.Combine( this.Value );
}
public static Boolean operator ==( $refinementname$$supertype$ left, $refinementname$$supertype$ right )
{
return left.Equals( other: right );
}
public static Boolean operator !=( $refinementname$$supertype$ left, $refinementname$$supertype$ right )
{
return !left.Equals( other: right );
}
#endregion
}$end$]]>
</Code>
<Imports>
<Import>
<Namespace>System</Namespace>
</Import>
<Import>
<Namespace>System.Diagnostics.CodeAnalysis</Namespace>
</Import>
</Imports>
</Snippet>
</CodeSnippet>
</CodeSnippets>
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/475878.html
上一篇:NET5Core專案上的MSBuild生成不同的bin/x64和bin/Debug檔案夾結構
下一篇:視覺C 和u8前綴
