diff --git a/src/Microsoft.Extensions.Primitives/StringValues.cs b/src/Microsoft.Extensions.Primitives/StringValues.cs index 68a2d13a369..7078720df63 100644 --- a/src/Microsoft.Extensions.Primitives/StringValues.cs +++ b/src/Microsoft.Extensions.Primitives/StringValues.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Text; using System.Collections; using System.Collections.Generic; using Microsoft.Extensions.Internal; @@ -11,26 +12,66 @@ namespace Microsoft.Extensions.Primitives /// /// Represents zero/null, one, or many strings in an efficient way. /// - public struct StringValues : IList, IReadOnlyList, IEquatable, IEquatable, IEquatable + public class StringValues : IList, IReadOnlyList, IEquatable, IEquatable, IEquatable { private static readonly string[] EmptyArray = new string[0]; public static readonly StringValues Empty = new StringValues(EmptyArray); private readonly string _value; private readonly string[] _values; + private readonly Encoding _encoding; + private readonly byte[] _bytes; public StringValues(string value) { _value = value; - _values = null; } public StringValues(string[] values) { - _value = null; _values = values; } + private StringValues(string value, Encoding encoding) + { + _value = value; + _encoding = encoding; + _bytes = _encoding.GetBytes(value); + } + + /// + /// Creates a new instance of with one string and compute its encoded bytes with the specified Encoding. + /// + /// The string to be encoded. + /// The to be use when computing pre-encoded bytes. + /// A which contains the pre-encoded bytes. + public static StringValues CreatePreEncoded(string value, Encoding encoding) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return new StringValues(value, encoding); + } + + /// + /// Try to retrieve the pre-encoded bytes of the . The return value indicates whether pre-encoded bytes exist. + /// + /// If successful, contains the pre-encoded bytes of the . + /// If successful, contains the used to compute the pre-encoded bytes. + /// true if contains pre-encoded bytes, otherwise false. + public bool TryGetPreEncoded(out byte[] bytes, out Encoding encoding) + { + bytes = _bytes; + encoding = _encoding; + return _bytes != null; + } + public static implicit operator StringValues(string value) { return new StringValues(value); @@ -206,7 +247,7 @@ void ICollection.Clear() public Enumerator GetEnumerator() { - return new Enumerator(ref this); + return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() @@ -424,7 +465,7 @@ public struct Enumerator : IEnumerator private string _current; private int _index; - public Enumerator(ref StringValues values) + public Enumerator(StringValues values) { _values = values._values; _current = values._value; diff --git a/test/Microsoft.Extensions.Primitives.Tests/StringValuesTests.cs b/test/Microsoft.Extensions.Primitives.Tests/StringValuesTests.cs index a0299065c81..0b580303335 100644 --- a/test/Microsoft.Extensions.Primitives.Tests/StringValuesTests.cs +++ b/test/Microsoft.Extensions.Primitives.Tests/StringValuesTests.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using Xunit; namespace Microsoft.Extensions.Primitives @@ -17,7 +18,6 @@ public static TheoryData DefaultOrNullStringValues { return new TheoryData { - new StringValues(), new StringValues((string)null), new StringValues((string[])null), (string)null, @@ -63,7 +63,6 @@ public static TheoryData FilledStringValuesWithExpectedStr { return new TheoryData { - { default(StringValues), (string)null }, { StringValues.Empty, (string)null }, { new StringValues(new string[] { }), (string)null }, { new StringValues(string.Empty), string.Empty }, @@ -79,7 +78,6 @@ public static TheoryData FilledStringValuesWithExpectedObj { return new TheoryData { - { default(StringValues), (object)null }, { StringValues.Empty, (object)null }, { new StringValues(new string[] { }), (object)null }, { new StringValues("abc"), (object)"abc" }, @@ -96,7 +94,6 @@ public static TheoryData FilledStringValuesWithExpected { return new TheoryData { - { default(StringValues), new string[0] }, { StringValues.Empty, new string[0] }, { new StringValues(string.Empty), new[] { string.Empty } }, { new StringValues("abc"), new[] { "abc" } }, @@ -333,8 +330,6 @@ public void DefaultNullOrEmpty_Concat(StringValues stringValues) string[] empty = new string[0]; Assert.Equal(empty, StringValues.Concat(stringValues, StringValues.Empty)); Assert.Equal(empty, StringValues.Concat(StringValues.Empty, stringValues)); - Assert.Equal(empty, StringValues.Concat(stringValues, new StringValues())); - Assert.Equal(empty, StringValues.Concat(new StringValues(), stringValues)); } [Theory] @@ -455,5 +450,71 @@ public void Equals_StringArray(StringValues stringValues, string[] expected) Assert.True(StringValues.Equals(stringValues, expected)); Assert.False(StringValues.Equals(stringValues, notEqual)); } + + [Theory] + [MemberData(nameof(DefaultOrNullStringValues))] + [MemberData(nameof(EmptyStringValues))] + [MemberData(nameof(FilledStringValues))] + public void TryGetConstant_NullDefaultEncodingAndEncodedBytes(StringValues stringValues) + { + Encoding encoding; + byte[] bytes; + + Assert.False(stringValues.TryGetPreEncoded(out bytes, out encoding)); + Assert.Null(encoding); + Assert.Null(bytes); + } + + [Theory] + [MemberData(nameof(DefaultOrNullStringValues))] + [MemberData(nameof(EmptyStringValues))] + public void CreateConstant_ThrowsForNullOrEmpty(StringValues stringValues) + { + Assert.Throws(() => (StringValues.CreatePreEncoded(stringValues, Encoding.ASCII))); + } + + [Fact] + public void CreateConstant_EncodesEmptyString() + { + var stringValueConstant = StringValues.CreatePreEncoded("", Encoding.ASCII); + byte[] bytes; + Encoding encoding; + + stringValueConstant.TryGetPreEncoded(out bytes, out encoding); + + Assert.NotNull(bytes); + Assert.Empty(bytes); + } + + [Fact] + public void CreateConstant_EncodesUsingSpecifiedEncoding() + { + var testString = "foo bar"; + var expectedBytes = Encoding.ASCII.GetBytes(testString); + byte[] outBytes; + Encoding outEncoding; + + var constantStringValues = StringValues.CreatePreEncoded(testString, Encoding.ASCII); + constantStringValues.TryGetPreEncoded(out outBytes, out outEncoding); + + Assert.Equal(expectedBytes, outBytes); + Assert.Equal(Encoding.ASCII, outEncoding); + } + + [Fact] + public void TryGetConstant_ReturnsIdenticalBytesAndEncoding() + { + var encoding = Encoding.GetEncoding(20127); // US-ASCII + var constantStringValues = StringValues.CreatePreEncoded("foo bar", encoding); + + byte[] bytes1, bytes2; + Encoding encoding1, encoding2; + constantStringValues.TryGetPreEncoded(out bytes1, out encoding1); + constantStringValues.TryGetPreEncoded(out bytes2, out encoding2); + + Assert.Same(bytes1, bytes2); + Assert.Same(encoding, encoding1); + Assert.Same(encoding, encoding2); + } } }