是否有任何方法,也许通过使用Contracts
,检查变量的值是否符合visualstudio2022设计期间的一些规则?
。int
在c#中被大量使用,但通常我们真正想要的是uint
,也就是说只有正值,标准是检查是否传递了负值以抛出错误/异常/等。
但是有没有办法写一个方法告诉Visual Studio或者编译器或者其他什么,这个参数必须是>= 0"每当开发人员传递的值小于0时,在Visual Studio错误列表中显示错误?
通常情况下代码契约将是完美的-但看起来令人沮丧的是,该功能在微软从。net Framework到。net Core的大跃进中不受欢迎和放弃,这意味着需要一些替代方法。
虽然使用Roslyn Code Analysis基本上可以重新实现代码契约,但这不是一项小任务,可能会花费您许多月来构建一个可以验证变量在其生命周期中可能的运行时边界的分析器(从数学上讲,在一般情况下不可能解决)。
因此,另一种选择是使用细化类型/谓词类型/依赖类型(我忘记了它们之间的确切区别)—但总体要点是使用一个新的、不同的类型来表示其包含值的约束。由于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 );
}
这是我自己在Visual Studio上的个人改进类型*.snippet
-我希望它对你有用:
<?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:UsersYouDocumentsVisual Studio {year}Code SnippetsVisual 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://learn.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>