#nullable enable
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Security;
namespace Vanara.InteropServices;
/// Base abstract class for a string handler based on .
/// The type of the memory.
///
public abstract class SafeMemString : SafeMemoryHandle, IConvertible, IComparable>, IComparable, IComparable, IEnumerable, IEnumerable, IEquatable>, IEquatable where TMem : IMemoryMethods, new()
{
/// The system default character set for evaluating CharSet.Auto.
protected static readonly CharSet defaultCharSet = Marshal.SystemDefaultCharSize == 2 ? CharSet.Unicode : CharSet.Ansi;
/// Initializes a new instance of the class.
/// The string value.
/// The character set.
protected SafeMemString(string? s, CharSet charSet = CharSet.Unicode) : this(s, s is null ? 0 : s.Length + 1, charSet)
{
}
/// Initializes a new instance of the class.
/// The string value.
/// The capacity of the buffer, in characters.
/// The character set.
protected SafeMemString(string? s, int capacity, CharSet charSet = CharSet.Unicode) : this(capacity, charSet) => StringHelper.Write(s, handle, out _, true, charSet, Size);
/// Initializes a new instance of the class.
/// The string value.
/// The character set.
protected SafeMemString(SecureString s, CharSet charSet = CharSet.Unicode) : this(IntPtr.Zero, charSet)
{
SetHandle(StringHelper.AllocSecureString(s, charSet, mm.AllocMem, out var sz));
base.sz = sz;
}
/// Initializes a new instance of the class.
/// The size of the buffer in characters, including the null character terminator.
/// The character set.
protected SafeMemString(int charLen, CharSet charSet = CharSet.Unicode) : base(charLen * StringHelper.GetCharSize(charSet)) => CharSet = charSet != CharSet.Auto ? charSet : defaultCharSet;
/// Prevents a default instance of the class from being created.
[ExcludeFromCodeCoverage]
protected SafeMemString() : base(0) { }
/// Initializes a new instance of the class.
/// The PTR.
/// The character set.
/// true to reliably release the handle during finalization; false to prevent it.
/// The number of bytes allocated to .
[ExcludeFromCodeCoverage]
protected SafeMemString(IntPtr ptr, CharSet charSet = CharSet.Unicode, bool ownsHandle = true, PInvoke.SizeT allocatedBytes = default) : base(ptr, allocatedBytes, ownsHandle) => CharSet = charSet == CharSet.Auto ? charSet : defaultCharSet;
/// Gets the number of allocated characters or 0 if the size is unknown (for example if it is holding a .
/// The number of allocated characters.
public int Capacity
{
get => Size / StringHelper.GetCharSize(CharSet);
set => Size = value * StringHelper.GetCharSize(CharSet);
}
/// Gets the character set of the assigned string.
/// The character set.
public CharSet CharSet { get; private set; } = CharSet.Unicode;
/// Gets a value indicating whether this instance contains a pointer.
/// if this instance is null; otherwise, .
public bool IsNull => handle == IntPtr.Zero;
/// Gets the number of characters in the current object.
public int Length => ToString()?.Length ?? 0;
/// Gets or sets the at the specified index.
/// The .
/// The index of the character in the in-memory string.
/// The character.
///
public char this[int index]
{
get
{
var cs = StringHelper.GetCharSize(CharSet);
return index * cs >= Capacity || index < 0
? throw new IndexOutOfRangeException()
: CharSet == CharSet.Ansi ? Encoding.UTF8.GetChars(GetBytes(index * cs, cs))[0] : Encoding.Unicode.GetChars(GetBytes(index * cs, cs))[0];
}
set
{
var cs = StringHelper.GetCharSize(CharSet);
if (index * cs >= Capacity || index < 0) throw new IndexOutOfRangeException();
var bytes = CharSet == CharSet.Ansi ? Encoding.UTF8.GetBytes(new[] { value }) : Encoding.Unicode.GetBytes(new[] { value });
handle.Write(bytes, index * cs, Size);
}
}
/// Performs an explicit conversion from to .
/// The instance.
/// The result of the conversion.
/// Cannot convert an ANSI string to a Char pointer.
public static unsafe explicit operator char*(SafeMemString s) => s.CharSet == CharSet.Unicode ? (char*)(void*)s.handle : throw new InvalidCastException("Cannot convert an ANSI string to a Char pointer.");
/// Returns the value of the field.
/// The instance.
///
/// An representing the value of the handle field. If the handle has been marked invalid with , this method still returns the original handle value, which can be a stale value.
///
public static implicit operator IntPtr(SafeMemString s) => s.DangerousGetHandle();
#if ALLOWSPAN
/// Performs an implicit conversion from to .
/// The value.
/// The result of the conversion.
public static implicit operator ReadOnlySpan(SafeMemString value) => value.AsReadOnlySpan(value.Length);
/// Performs an implicit conversion from to .
/// The value.
/// The result of the conversion.
public static implicit operator Span(SafeMemString value) => value.AsSpan(value.Length);
#endif
/// Returns the string value held by a .
/// The instance.
/// A value held by the or null if the handle or value is invalid.
public static implicit operator string?(SafeMemString s) => s.ToString();
/// Implements the operator !=.
/// The left value.
/// The right value.
/// The result of the operator.
public static bool operator !=(SafeMemString? left, SafeMemString? right) => !(left == right);
/// Implements the operator !=.
/// The left value.
/// The right value.
/// The result of the operator.
public static bool operator !=(SafeMemString? left, string right) => !(left == right);
/// Implements the operator <.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator <(SafeMemString? left, SafeMemString? right) => left is null || left.IsNull ? right is not null && !right.IsNull : left.CompareTo(right) < 0;
/// Implements the operator <.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator <(SafeMemString? left, string right) => left is null || left.IsNull ? right is not null : left.CompareTo(right) < 0;
/// Implements the operator <=.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator <=(SafeMemString? left, SafeMemString? right) => left is null || left.IsNull || left.CompareTo(right) <= 0;
/// Implements the operator <=.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator <=(SafeMemString? left, string right) => left is null || left.IsNull || left.CompareTo(right) <= 0;
/// Implements the operator ==.
/// The left value.
/// The right value.
/// The result of the operator.
public static bool operator ==(SafeMemString? left, SafeMemString? right) => left is null || left.IsNull ? right is null || right.IsNull : left.Equals(right);
/// Implements the operator ==.
/// The left value.
/// The right value.
/// The result of the operator.
public static bool operator ==(SafeMemString? left, string right) => left is null || left.IsNull ? right is null : left.Equals(right);
/// Implements the operator >.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator >(SafeMemString? left, SafeMemString? right) => left is not null && !left.IsNull && left.CompareTo(right) > 0;
/// Implements the operator >.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator >(SafeMemString? left, string right) => left is not null && !left.IsNull && left.CompareTo(right) > 0;
/// Implements the operator >=.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator >=(SafeMemString? left, SafeMemString? right) => left is null || left.IsNull ? right is null || right.IsNull : left.CompareTo(right) >= 0;
/// Implements the operator >=.
/// The left.
/// The right.
/// The result of the operator.
public static bool operator >=(SafeMemString? left, string right) => left is null || left.IsNull ? right is null : left.CompareTo(right) >= 0;
/// Removes all characters from the current instance.
public void Clear()
{ Capacity = 1; this[0] = '\0'; }
/// Compares the current object with another object of the same type.
/// An object to compare with this object.
///
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
///
///
///
/// Value
/// Meaning
///
/// -
/// Less than zero
/// This object is less than the parameter.
///
/// -
/// Zero
/// This object is equal to .
///
/// -
/// Greater than zero
/// This object is greater than .
///
///
///
public int CompareTo(SafeMemString? other) => string.Compare(ToString(), other?.ToString());
/// Compares the current object with a .
/// An object to compare with this object.
///
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
///
///
///
/// Value
/// Meaning
///
/// -
/// Less than zero
/// This object is less than the parameter.
///
/// -
/// Zero
/// This object is equal to .
///
/// -
/// Greater than zero
/// This object is greater than .
///
///
///
public int CompareTo(string? other) => string.Compare(ToString(), other);
///
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance
/// precedes, follows, or occurs in the same position in the sort order as the other object.
///
/// An object to compare with this object.
///
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
///
///
///
/// Value
/// Meaning
///
/// -
/// Less than zero
/// This object is less than the parameter.
///
/// -
/// Zero
/// This object is equal to .
///
/// -
/// Greater than zero
/// This object is greater than .
///
///
///
public int CompareTo(object? other) => other switch
{
null => IsNull ? 0 : 1,
string s => CompareTo(s),
SafeMemString v => CompareTo(v),
_ => throw new ArgumentException($"Argument must be a string or {nameof(SafeMemString)}", nameof(other))
};
/// Indicates whether the current object is equal to another object of the same type.
/// An object to compare with this object.
/// true if the current object is equal to the parameter; otherwise, false.
public bool Equals(SafeMemString? other) => string.Equals(ToString(), other?.ToString());
/// Indicates whether the current object is equal to another object of the same type.
/// An object to compare with this object.
/// true if the current object is equal to the parameter; otherwise, false.
public bool Equals(string? other) => string.Equals(ToString(), other);
/// Determines whether the specified , is equal to this instance.
/// The to compare with this instance.
/// true if the specified is equal to this instance; otherwise, false.
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || obj switch
{
null => false,
SafeMemString ms => Equals(ms),
string s => Equals(s),
SafeAllocatedMemoryHandle m => m == handle,
_ => false,
};
/// Retrieves an enumerator that can iterate through the individual characters in this string.
/// An object that can be used to iterate through individual characters in this string.
public IEnumerator GetEnumerator() => ToString()?.GetEnumerator() ?? System.Linq.Enumerable.Empty().GetEnumerator();
/// Returns a hash code for this instance.
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
public override int GetHashCode() => ToString()?.GetHashCode() ?? 0;
/// Assigns a new string to this memory.
///
/// The string value. This value can be , but its length cannot be greater than the current .
///
public virtual void Set(string? value)
{
if (value is null)
ReleaseHandle();
else
StringHelper.Write(value, handle, out _, true, CharSet, Size);
}
/// Returns the string value held by this instance.
/// A value held by this instance or null if the handle is invalid.
public override string? ToString() => IsInvalid ? null : StringHelper.GetString(handle, CharSet, Size == 0 ? long.MaxValue : (long)Size);
///
/// Converts the value of this instance to an equivalent using the specified culture-specific formatting information.
///
/// An interface implementation that supplies culture-specific formatting information.
/// A instance equivalent to the value of this instance.
public virtual string ToString(IFormatProvider? provider) => ToString()?.ToString(provider) ?? string.Empty;
/// Returns an enumerator that iterates through a collection.
/// An object that can be used to iterate through the collection.
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
///
TypeCode IConvertible.GetTypeCode() => TypeCode.String;
///
bool IConvertible.ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(ToString(), provider);
///
byte IConvertible.ToByte(IFormatProvider? provider) => Convert.ToByte(ToString(), provider);
///
char IConvertible.ToChar(IFormatProvider? provider) => Convert.ToChar(ToString() ?? throw new ArgumentNullException(), provider);
///
DateTime IConvertible.ToDateTime(IFormatProvider? provider) => Convert.ToDateTime(ToString(), provider);
///
decimal IConvertible.ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(ToString(), provider);
///
double IConvertible.ToDouble(IFormatProvider? provider) => Convert.ToDouble(ToString(), provider);
///
short IConvertible.ToInt16(IFormatProvider? provider) => Convert.ToInt16(ToString(), provider);
///
int IConvertible.ToInt32(IFormatProvider? provider) => Convert.ToInt32(ToString(), provider);
///
long IConvertible.ToInt64(IFormatProvider? provider) => Convert.ToInt64(ToString(), provider);
///
sbyte IConvertible.ToSByte(IFormatProvider? provider) => Convert.ToSByte(ToString() ?? "0", provider);
///
float IConvertible.ToSingle(IFormatProvider? provider) => Convert.ToSingle(ToString(), provider);
///
object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
{
var str = ToString();
return str is not null ? ((IConvertible)str).ToType(conversionType, provider) : throw new ArgumentNullException();
}
///
ushort IConvertible.ToUInt16(IFormatProvider? provider) => Convert.ToUInt16(ToString(), provider);
///
uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(ToString(), provider);
///
ulong IConvertible.ToUInt64(IFormatProvider? provider) => Convert.ToUInt64(ToString(), provider);
/// Appends a copy of the specified string to this instance.
/// The string to append.
/// A reference to this instance after the append operation has completed.
protected virtual void Append(string? value)
{
if (!string.IsNullOrEmpty(value))
{
var curLen = Length;
Capacity += value?.Length ?? 0;
StringHelper.Write(value, DangerousGetHandle().Offset(curLen * 2), out _, true, CharSet.Unicode, Size);
}
}
/// Inserts a string into this instance at the specified character position.
/// The position in this instance where insertion begins.
/// The string to insert.
///
/// is less than zero or greater than the current length of this instance.
///
protected virtual void Insert(int index, string? value)
{
if (index > Length || index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (value is not null && value != string.Empty)
{
var curLen = Length;
Capacity += value.Length;
handle.Offset(index * 2).CopyTo(handle.Offset(curLen * 2), 2 * (curLen - index + 1));
StringHelper.Write(value, handle.Offset(index * 2), out _, false, CharSet.Unicode, Size);
}
}
/// Removes the specified range of characters from this instance.
/// The zero-based position in this instance where removal begins.
/// The number of characters to remove.
///
/// If or is less than zero, or + is greater than the length of this instance.
///
protected virtual void Remove(int startIndex, int length)
{
if (startIndex > Length || startIndex < 0 || startIndex + length > Length)
throw new ArgumentOutOfRangeException(nameof(startIndex));
handle.Offset((startIndex + length) * 2).CopyTo(handle.Offset(startIndex * 2), 2 * length);
if (length > 64)
Capacity = Length + 1;
}
/// Replaces the specified old value.
/// The string to replace.
/// The string that replaces , or .
/// The position in this instance where the substring begins.
/// The length of the substring.
protected virtual void Replace(string oldValue, string? newValue, int startIndex, int count)
{
if (IsNull || Length == 0) return;
var sb = new StringBuilder(ToString());
sb.Replace(oldValue, newValue, startIndex, count);
Capacity = sb.Length;
Set(sb.ToString());
}
/// Replaces, within a substring of this instance, all occurrences of a specified character with another specified character.
/// The character to replace.
/// The character that replaces .
/// The position in this instance where the substring begins.
/// The length of the substring.
///
/// + is greater than the length of the value of this instance.
/// -or-
/// or is less than zero.
///
protected virtual void Replace(char oldChar, char newChar, int startIndex, int count)
{
var currentLength = Length;
if ((uint)startIndex > (uint)currentLength)
throw new ArgumentOutOfRangeException(nameof(startIndex));
if (count < 0 || startIndex > currentLength - count)
throw new ArgumentOutOfRangeException(nameof(count));
var endIndex = startIndex + count;
var chunk = (Span)this;
for (var i = startIndex; i < endIndex; i++)
{
if (chunk[i] == oldChar)
chunk[i] = newChar;
}
}
}