diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 0c18b39a3..40517bfc8 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -405,6 +405,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
+ typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors;
return typeSystemAstBuilder;
}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 81f3f0c6e..1fc2b44bd 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -1927,7 +1927,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword("get", PropertyDeclaration.GetKeywordRole);
style = policy.PropertyGetBraceStyle;
} else if (accessor.Role == PropertyDeclaration.SetterRole) {
- WriteKeyword("set", PropertyDeclaration.SetKeywordRole);
+ if (accessor.Keyword.Role == PropertyDeclaration.InitKeywordRole) {
+ WriteKeyword("init", PropertyDeclaration.InitKeywordRole);
+ } else {
+ WriteKeyword("set", PropertyDeclaration.SetKeywordRole);
+ }
style = policy.PropertySetBraceStyle;
} else if (accessor.Role == CustomEventDeclaration.AddAccessorRole) {
WriteKeyword("add", CustomEventDeclaration.AddKeywordRole);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs
index dd41e83b7..345aa9aca 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs
@@ -72,13 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
///
- /// Gets the 'get'/'set'/'add'/'remove' keyword
+ /// Gets the 'get'/'set'/'init'/'add'/'remove' keyword
///
public CSharpTokenNode Keyword {
get {
for (AstNode child = this.FirstChild; child != null; child = child.NextSibling) {
if (child.Role == PropertyDeclaration.GetKeywordRole || child.Role == PropertyDeclaration.SetKeywordRole
- || child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole)
+ || child.Role == PropertyDeclaration.InitKeywordRole
+ || child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole)
{
return (CSharpTokenNode)child;
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs
index 00f163122..34ac8039a 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs
@@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public static readonly TokenRole GetKeywordRole = new TokenRole ("get");
public static readonly TokenRole SetKeywordRole = new TokenRole ("set");
+ public static readonly TokenRole InitKeywordRole = new TokenRole ("init");
public static readonly Role GetterRole = new Role("Getter", Accessor.Null);
public static readonly Role SetterRole = new Role("Setter", Accessor.Null);
public static readonly Role ExpressionBodyRole = new Role("ExpressionBody", Expression.Null);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index 33464c99a..1f944a664 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
+using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.Semantics;
@@ -205,6 +206,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// The default value is .
///
public bool PrintIntegralValuesAsHex { get; set; }
+
+ ///
+ /// Controls whether C# 9 "init;" accessors are supported.
+ /// If disabled, emits "set /*init*/;" instead.
+ ///
+ public bool SupportInitAccessors { get; set; }
#endregion
#region Convert Type
@@ -1363,7 +1370,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ConvertDestructor((IMethod)entity);
case SymbolKind.Accessor:
IMethod accessor = (IMethod)entity;
- return ConvertAccessor(accessor, accessor.AccessorOwner != null ? accessor.AccessorOwner.Accessibility : Accessibility.None, false);
+ Accessibility ownerAccessibility = accessor.AccessorOwner?.Accessibility ?? Accessibility.None;
+ return ConvertAccessor(accessor, accessor.AccessorKind, ownerAccessibility, false);
default:
throw new ArgumentException("Invalid value for SymbolKind: " + entity.SymbolKind);
}
@@ -1554,15 +1562,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
- Accessor ConvertAccessor(IMethod accessor, Accessibility ownerAccessibility, bool addParameterAttribute)
+ Accessor ConvertAccessor(IMethod accessor, MethodSemanticsAttributes kind, Accessibility ownerAccessibility, bool addParameterAttribute)
{
if (accessor == null)
return Accessor.Null;
Accessor decl = new Accessor();
- if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
- decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
- if (accessor.HasReadonlyModifier())
- decl.Modifiers |= Modifiers.Readonly;
if (ShowAttributes) {
decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes()));
decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return"));
@@ -1570,10 +1574,35 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
decl.Attributes.AddRange(ConvertAttributes(accessor.Parameters.Last().GetAttributes(), "param"));
}
}
+ if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
+ decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
+ if (accessor.HasReadonlyModifier())
+ decl.Modifiers |= Modifiers.Readonly;
+ TokenRole keywordRole = kind switch
+ {
+ MethodSemanticsAttributes.Getter => PropertyDeclaration.GetKeywordRole,
+ MethodSemanticsAttributes.Setter => PropertyDeclaration.SetKeywordRole,
+ MethodSemanticsAttributes.Adder => CustomEventDeclaration.AddKeywordRole,
+ MethodSemanticsAttributes.Remover => CustomEventDeclaration.RemoveKeywordRole,
+ _ => null
+ };
+ if (kind == MethodSemanticsAttributes.Setter && SupportInitAccessors && accessor.IsInitOnly) {
+ keywordRole = PropertyDeclaration.InitKeywordRole;
+ }
+ if (keywordRole != null) {
+ decl.AddChild(new CSharpTokenNode(TextLocation.Empty, keywordRole), keywordRole);
+ }
+ if (accessor.IsInitOnly && keywordRole != PropertyDeclaration.InitKeywordRole) {
+ decl.AddChild(new Comment("init", CommentType.MultiLine), Roles.Comment);
+ }
if (AddResolveResultAnnotations) {
decl.AddAnnotation(new MemberResolveResult(null, accessor));
}
- decl.Body = GenerateBodyBlock();
+ if (GenerateBody) {
+ decl.Body = GenerateBodyBlock();
+ } else {
+ decl.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.Semicolon), Roles.Semicolon);
+ }
return decl;
}
@@ -1592,8 +1621,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ct.HasReadOnlySpecifier = true;
}
decl.Name = property.Name;
- decl.Getter = ConvertAccessor(property.Getter, property.Accessibility, false);
- decl.Setter = ConvertAccessor(property.Setter, property.Accessibility, true);
+ decl.Getter = ConvertAccessor(property.Getter, MethodSemanticsAttributes.Getter, property.Accessibility, false);
+ decl.Setter = ConvertAccessor(property.Setter, MethodSemanticsAttributes.Setter, property.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (property);
MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
@@ -1624,8 +1653,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
foreach (IParameter p in indexer.Parameters) {
decl.Parameters.Add(ConvertParameter(p));
}
- decl.Getter = ConvertAccessor(indexer.Getter, indexer.Accessibility, false);
- decl.Setter = ConvertAccessor(indexer.Setter, indexer.Accessibility, true);
+ decl.Getter = ConvertAccessor(indexer.Getter, MethodSemanticsAttributes.Getter, indexer.Accessibility, false);
+ decl.Setter = ConvertAccessor(indexer.Setter, MethodSemanticsAttributes.Setter, indexer.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (indexer);
MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
@@ -1644,8 +1673,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
decl.ReturnType = ConvertType(ev.ReturnType);
decl.Name = ev.Name;
- decl.AddAccessor = ConvertAccessor(ev.AddAccessor, ev.Accessibility, true);
- decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, ev.Accessibility, true);
+ decl.AddAccessor = ConvertAccessor(ev.AddAccessor, MethodSemanticsAttributes.Adder, ev.Accessibility, true);
+ decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, MethodSemanticsAttributes.Remover, ev.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (ev);
MergeReadOnlyModifiers(decl, decl.AddAccessor, decl.RemoveAccessor);
return decl;
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index e1b158b0a..26bd075b0 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -116,12 +116,13 @@ namespace ICSharpCode.Decompiler
}
if (languageVersion < CSharp.LanguageVersion.Preview) {
nativeIntegers = false;
+ initAccessors = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
- if (nativeIntegers)
+ if (nativeIntegers || initAccessors)
return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0;
@@ -163,6 +164,23 @@ namespace ICSharpCode.Decompiler
}
}
+ bool initAccessors = true;
+
+ ///
+ /// Use C# 9 init; property accessors.
+ ///
+ [Category("C# 9.0 (experimental)")]
+ [Description("DecompilerSettings.InitAccessors")]
+ public bool InitAccessors {
+ get { return initAccessors; }
+ set {
+ if (initAccessors != value) {
+ initAccessors = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool anonymousMethods = true;
///
diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
index ad1c177d6..b12b4f064 100644
--- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
@@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
///
bool ReturnTypeIsRefReadOnly { get; }
+ ///
+ /// Gets whether this method may only be called on fresh instances.
+ /// Used with C# 9 `init;` property setters.
+ ///
+ bool IsInitOnly { get; }
+
///
/// Gets whether the method accepts the 'this' reference as ref readonly.
/// This can be either because the method is C# 8.0 'readonly', or because it is within a C# 7.2 'readonly struct'
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs
index 2a11babee..837539720 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs
@@ -134,6 +134,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance;
bool IMethod.ReturnTypeIsRefReadOnly => false;
bool IMethod.ThisIsRefReadOnly => false;
+ bool IMethod.IsInitOnly => false;
public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance;
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs
index 1139366f1..1e14fa85b 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs
@@ -147,6 +147,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes();
bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly;
+ bool IMethod.IsInitOnly => baseMethod.IsInitOnly;
///
/// We consider local functions as always static, because they do not have a "this parameter".
/// Even local functions in instance methods capture this.
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
index ab02b8252..a015d7d3d 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
@@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IType returnType;
byte returnTypeIsRefReadonly = ThreeState.Unknown;
byte thisIsRefReadonly = ThreeState.Unknown;
+ bool isInitOnly;
internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle)
{
@@ -150,6 +151,15 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
}
}
+ public bool IsInitOnly {
+ get {
+ var returnType = LazyInit.VolatileRead(ref this.returnType);
+ if (returnType == null)
+ DecodeSignature();
+ return this.isInitOnly;
+ }
+ }
+
internal Nullability NullableContext {
get {
var methodDef = module.metadata.GetMethodDefinition(handle);
@@ -163,19 +173,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
var genericContext = new GenericContext(DeclaringType.TypeParameters, this.TypeParameters);
IType returnType;
IParameter[] parameters;
+ ModifiedType mod;
try {
var nullableContext = methodDef.GetCustomAttributes().GetNullableContext(module.metadata) ?? DeclaringTypeDefinition.NullableContext;
var signature = methodDef.DecodeSignature(module.TypeProvider, genericContext);
- (returnType, parameters) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this));
+ (returnType, parameters, mod) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this));
} catch (BadImageFormatException) {
returnType = SpecialType.UnknownType;
parameters = Empty.Array;
+ mod = null;
}
+ this.isInitOnly = mod is { Modifier: { Name: "IsExternalInit", Namespace: "System.Runtime.CompilerServices" } };
LazyInit.GetOrSet(ref this.returnType, returnType);
LazyInit.GetOrSet(ref this.parameters, parameters);
}
- internal static (IType, IParameter[]) DecodeSignature(MetadataModule module, IParameterizedMember owner,
+ internal static (IType returnType, IParameter[] parameters, ModifiedType returnTypeModifier) DecodeSignature(
+ MetadataModule module, IParameterizedMember owner,
MethodSignature signature, ParameterHandleCollection? parameterHandles,
Nullability nullableContext, TypeSystemOptions typeSystemOptions,
CustomAttributeHandleCollection? returnTypeAttributes = null)
@@ -231,7 +245,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
Debug.Assert(i == parameters.Length);
var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType,
module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext);
- return (returnType, parameters);
+ return (returnType, parameters, signature.ReturnType as ModifiedType);
}
#endregion
@@ -453,7 +467,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
#endregion
public Accessibility Accessibility => GetAccessibility(attributes);
-
+
internal static Accessibility GetAccessibility(MethodAttributes attr)
{
switch (attr & MethodAttributes.MemberAccessMask) {
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs
index 83596cb51..2f33e3d17 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs
@@ -153,7 +153,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
// Roslyn uses the same workaround (see the NullableTypeDecoder.TransformType
// call in PEPropertySymbol).
var typeOptions = module.OptionsForEntity(declTypeDef);
- (returnType, parameters) = MetadataMethod.DecodeSignature(module, this, signature,
+ (returnType, parameters, _) = MetadataMethod.DecodeSignature(
+ module, this, signature,
parameterHandles, nullableContext, typeOptions,
returnTypeAttributes: propertyDef.GetCustomAttributes());
} catch (BadImageFormatException) {
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs
index 58d1e2f56..7ec70de21 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs
@@ -98,6 +98,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public bool ReturnTypeIsRefReadOnly => methodDefinition.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => methodDefinition.ThisIsRefReadOnly;
+ bool IMethod.IsInitOnly => methodDefinition.IsInitOnly;
public IReadOnlyList TypeParameters {
get {
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
index 9d79e1b79..0acaf9d28 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
@@ -62,6 +62,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly;
+ bool IMethod.IsInitOnly => underlyingMethod.IsInitOnly;
IReadOnlyList IMethod.TypeParameters => EmptyList.Instance;
IReadOnlyList IMethod.TypeArguments => EmptyList.Instance;
diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs
index ecc0cbf22..6b53caa88 100644
--- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs
@@ -115,6 +115,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes();
bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly;
+ bool IMethod.IsInitOnly => baseMethod.IsInitOnly;
public IReadOnlyList TypeParameters {
get { return baseMethod.TypeParameters; }
diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
index 6845088a3..f588cd153 100644
--- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
+++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
@@ -184,8 +184,10 @@ namespace ICSharpCode.ILSpy
case "set":
case "add":
case "remove":
+ case "init":
if (role == PropertyDeclaration.GetKeywordRole ||
role == PropertyDeclaration.SetKeywordRole ||
+ role == PropertyDeclaration.InitKeywordRole ||
role == CustomEventDeclaration.AddKeywordRole ||
role == CustomEventDeclaration.RemoveKeywordRole)
color = accessorKeywordsColor;