C#: Implement readonly-ness in Array and Dictionary

- Expose `IsReadOnly` and add `MakeReadOnly` method.
This commit is contained in:
Raul Santos 2023-01-24 17:53:51 +01:00
parent 518b9e5801
commit 1aa54141e6
No known key found for this signature in database
GPG key ID: B532473AE3A803E4
5 changed files with 297 additions and 15 deletions

View file

@ -189,10 +189,15 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Resizes this <see cref="Array"/> to the given size. /// Resizes this <see cref="Array"/> to the given size.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="newSize">The new size of the array.</param> /// <param name="newSize">The new size of the array.</param>
/// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
public Error Resize(int newSize) public Error Resize(int newSize)
{ {
ThrowIfReadOnly();
var self = (godot_array)NativeValue; var self = (godot_array)NativeValue;
return NativeFuncs.godotsharp_array_resize(ref self, newSize); return NativeFuncs.godotsharp_array_resize(ref self, newSize);
} }
@ -200,8 +205,13 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Shuffles the contents of this <see cref="Array"/> into a random order. /// Shuffles the contents of this <see cref="Array"/> into a random order.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
public void Shuffle() public void Shuffle()
{ {
ThrowIfReadOnly();
var self = (godot_array)NativeValue; var self = (godot_array)NativeValue;
NativeFuncs.godotsharp_array_shuffle(ref self); NativeFuncs.godotsharp_array_shuffle(ref self);
} }
@ -240,6 +250,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Returns the item at the given <paramref name="index"/>. /// Returns the item at the given <paramref name="index"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the array is read-only.
/// </exception>
/// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value>
public unsafe Variant this[int index] public unsafe Variant this[int index]
{ {
@ -250,8 +263,11 @@ namespace Godot.Collections
} }
set set
{ {
ThrowIfReadOnly();
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
var self = (godot_array)NativeValue; var self = (godot_array)NativeValue;
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index]; godot_variant* itemPtr = &ptrw[index];
@ -264,9 +280,14 @@ namespace Godot.Collections
/// Adds an item to the end of this <see cref="Array"/>. /// Adds an item to the end of this <see cref="Array"/>.
/// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="item">The <see cref="Variant"/> item to add.</param> /// <param name="item">The <see cref="Variant"/> item to add.</param>
public void Add(Variant item) public void Add(Variant item)
{ {
ThrowIfReadOnly();
godot_variant variantValue = (godot_variant)item.NativeVar; godot_variant variantValue = (godot_variant)item.NativeVar;
var self = (godot_array)NativeValue; var self = (godot_array)NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue); _ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
@ -282,6 +303,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Erases all items from this <see cref="Array"/>. /// Erases all items from this <see cref="Array"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
public void Clear() => Resize(0); public void Clear() => Resize(0);
/// <summary> /// <summary>
@ -303,10 +327,15 @@ namespace Godot.Collections
/// or the position at the end of the array. /// or the position at the end of the array.
/// Existing items will be moved to the right. /// Existing items will be moved to the right.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="index">The index to insert at.</param> /// <param name="index">The index to insert at.</param>
/// <param name="item">The <see cref="Variant"/> item to insert.</param> /// <param name="item">The <see cref="Variant"/> item to insert.</param>
public void Insert(int index, Variant item) public void Insert(int index, Variant item)
{ {
ThrowIfReadOnly();
if (index < 0 || index > Count) if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
@ -319,9 +348,14 @@ namespace Godot.Collections
/// Removes the first occurrence of the specified <paramref name="item"/> /// Removes the first occurrence of the specified <paramref name="item"/>
/// from this <see cref="Array"/>. /// from this <see cref="Array"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="item">The value to remove.</param> /// <param name="item">The value to remove.</param>
public bool Remove(Variant item) public bool Remove(Variant item)
{ {
ThrowIfReadOnly();
int index = IndexOf(item); int index = IndexOf(item);
if (index >= 0) if (index >= 0)
{ {
@ -335,9 +369,14 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Removes an element from this <see cref="Array"/> by index. /// Removes an element from this <see cref="Array"/> by index.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="index">The index of the element to remove.</param> /// <param name="index">The index of the element to remove.</param>
public void RemoveAt(int index) public void RemoveAt(int index)
{ {
ThrowIfReadOnly();
if (index < 0 || index > Count) if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
@ -358,7 +397,28 @@ namespace Godot.Collections
object ICollection.SyncRoot => false; object ICollection.SyncRoot => false;
bool ICollection<Variant>.IsReadOnly => false; /// <summary>
/// Returns <see langword="true"/> if the array is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly;
/// <summary>
/// Makes the <see cref="Array"/> read-only, i.e. disabled modying of the
/// array's elements. Does not apply to nested content, e.g. content of
/// nested arrays.
/// </summary>
public void MakeReadOnly()
{
if (IsReadOnly)
{
// Avoid interop call when the array is already read-only.
return;
}
var self = (godot_array)NativeValue;
NativeFuncs.godotsharp_array_make_read_only(ref self);
}
/// <summary> /// <summary>
/// Copies the elements of this <see cref="Array"/> to the given /// Copies the elements of this <see cref="Array"/> to the given
@ -472,6 +532,14 @@ namespace Godot.Collections
{ {
elem = NativeValue.DangerousSelfRef.Elements[index]; elem = NativeValue.DangerousSelfRef.Elements[index];
} }
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Array instance is read-only.");
}
}
} }
internal interface IGenericGodotArray internal interface IGenericGodotArray
@ -592,6 +660,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Resizes this <see cref="Array{T}"/> to the given size. /// Resizes this <see cref="Array{T}"/> to the given size.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="newSize">The new size of the array.</param> /// <param name="newSize">The new size of the array.</param>
/// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
public Error Resize(int newSize) public Error Resize(int newSize)
@ -602,6 +673,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Shuffles the contents of this <see cref="Array{T}"/> into a random order. /// Shuffles the contents of this <see cref="Array{T}"/> into a random order.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
public void Shuffle() public void Shuffle()
{ {
_underlyingArray.Shuffle(); _underlyingArray.Shuffle();
@ -634,6 +708,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Returns the value at the given <paramref name="index"/>. /// Returns the value at the given <paramref name="index"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the array is read-only.
/// </exception>
/// <value>The value at the given <paramref name="index"/>.</value> /// <value>The value at the given <paramref name="index"/>.</value>
public unsafe T this[int index] public unsafe T this[int index]
{ {
@ -644,8 +721,11 @@ namespace Godot.Collections
} }
set set
{ {
ThrowIfReadOnly();
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
var self = (godot_array)_underlyingArray.NativeValue; var self = (godot_array)_underlyingArray.NativeValue;
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index]; godot_variant* itemPtr = &ptrw[index];
@ -673,10 +753,15 @@ namespace Godot.Collections
/// or the position at the end of the array. /// or the position at the end of the array.
/// Existing items will be moved to the right. /// Existing items will be moved to the right.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="index">The index to insert at.</param> /// <param name="index">The index to insert at.</param>
/// <param name="item">The item to insert.</param> /// <param name="item">The item to insert.</param>
public void Insert(int index, T item) public void Insert(int index, T item)
{ {
ThrowIfReadOnly();
if (index < 0 || index > Count) if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
@ -688,6 +773,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Removes an element from this <see cref="Array{T}"/> by index. /// Removes an element from this <see cref="Array{T}"/> by index.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="index">The index of the element to remove.</param> /// <param name="index">The index of the element to remove.</param>
public void RemoveAt(int index) public void RemoveAt(int index)
{ {
@ -703,16 +791,35 @@ namespace Godot.Collections
/// <returns>The number of elements.</returns> /// <returns>The number of elements.</returns>
public int Count => _underlyingArray.Count; public int Count => _underlyingArray.Count;
bool ICollection<T>.IsReadOnly => false; /// <summary>
/// Returns <see langword="true"/> if the array is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => _underlyingArray.IsReadOnly;
/// <summary>
/// Makes the <see cref="Array{T}"/> read-only, i.e. disabled modying of the
/// array's elements. Does not apply to nested content, e.g. content of
/// nested arrays.
/// </summary>
public void MakeReadOnly()
{
_underlyingArray.MakeReadOnly();
}
/// <summary> /// <summary>
/// Adds an item to the end of this <see cref="Array{T}"/>. /// Adds an item to the end of this <see cref="Array{T}"/>.
/// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="item">The item to add.</param> /// <param name="item">The item to add.</param>
/// <returns>The new size after adding the item.</returns> /// <returns>The new size after adding the item.</returns>
public void Add(T item) public void Add(T item)
{ {
ThrowIfReadOnly();
using var variantValue = VariantUtils.CreateFrom(item); using var variantValue = VariantUtils.CreateFrom(item);
var self = (godot_array)_underlyingArray.NativeValue; var self = (godot_array)_underlyingArray.NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue); _ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
@ -721,6 +828,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Erases all items from this <see cref="Array{T}"/>. /// Erases all items from this <see cref="Array{T}"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
public void Clear() public void Clear()
{ {
_underlyingArray.Clear(); _underlyingArray.Clear();
@ -769,10 +879,15 @@ namespace Godot.Collections
/// Removes the first occurrence of the specified value /// Removes the first occurrence of the specified value
/// from this <see cref="Array{T}"/>. /// from this <see cref="Array{T}"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The array is read-only.
/// </exception>
/// <param name="item">The value to remove.</param> /// <param name="item">The value to remove.</param>
/// <returns>A <see langword="bool"/> indicating success or failure.</returns> /// <returns>A <see langword="bool"/> indicating success or failure.</returns>
public bool Remove(T item) public bool Remove(T item)
{ {
ThrowIfReadOnly();
int index = IndexOf(item); int index = IndexOf(item);
if (index >= 0) if (index >= 0)
{ {
@ -812,5 +927,13 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Array<T>(Variant from) => from.AsGodotArray<T>(); public static explicit operator Array<T>(Variant from) => from.AsGodotArray<T>();
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Array instance is read-only.");
}
}
} }
} }

View file

@ -93,10 +93,15 @@ namespace Godot.Collections
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/> /// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>. /// is <see langword="true"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param> /// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param> /// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary dictionary, bool overwrite = false) public void Merge(Dictionary dictionary, bool overwrite = false)
{ {
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
var other = (godot_dictionary)dictionary.NativeValue; var other = (godot_dictionary)dictionary.NativeValue;
NativeFuncs.godotsharp_dictionary_merge(ref self, in other, overwrite.ToGodotBool()); NativeFuncs.godotsharp_dictionary_merge(ref self, in other, overwrite.ToGodotBool());
@ -175,8 +180,12 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Returns the value at the given <paramref name="key"/>. /// Returns the value at the given <paramref name="key"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the dictionary is read-only.
/// </exception>
/// <exception cref="KeyNotFoundException"> /// <exception cref="KeyNotFoundException">
/// An entry for <paramref name="key"/> does not exist in the dictionary. /// The property is retrieved and an entry for <paramref name="key"/>
/// does not exist in the dictionary.
/// </exception> /// </exception>
/// <value>The value at the given <paramref name="key"/>.</value> /// <value>The value at the given <paramref name="key"/>.</value>
public Variant this[Variant key] public Variant this[Variant key]
@ -197,6 +206,8 @@ namespace Godot.Collections
} }
set set
{ {
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self, NativeFuncs.godotsharp_dictionary_set_value(ref self,
(godot_variant)key.NativeVar, (godot_variant)value.NativeVar); (godot_variant)key.NativeVar, (godot_variant)value.NativeVar);
@ -207,6 +218,9 @@ namespace Godot.Collections
/// Adds an value <paramref name="value"/> at key <paramref name="key"/> /// Adds an value <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary"/>. /// to this <see cref="Dictionary"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// An entry for <paramref name="key"/> already exists in the dictionary. /// An entry for <paramref name="key"/> already exists in the dictionary.
/// </exception> /// </exception>
@ -214,6 +228,8 @@ namespace Godot.Collections
/// <param name="value">The value to add.</param> /// <param name="value">The value to add.</param>
public void Add(Variant key, Variant value) public void Add(Variant key, Variant value)
{ {
ThrowIfReadOnly();
var variantKey = (godot_variant)key.NativeVar; var variantKey = (godot_variant)key.NativeVar;
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
@ -230,8 +246,13 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Clears the dictionary, removing all entries from it. /// Clears the dictionary, removing all entries from it.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
public void Clear() public void Clear()
{ {
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_clear(ref self); NativeFuncs.godotsharp_dictionary_clear(ref self);
} }
@ -267,15 +288,22 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Removes an element from this <see cref="Dictionary"/> by key. /// Removes an element from this <see cref="Dictionary"/> by key.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="key">The key of the element to remove.</param> /// <param name="key">The key of the element to remove.</param>
public bool Remove(Variant key) public bool Remove(Variant key)
{ {
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool(); return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool();
} }
bool ICollection<KeyValuePair<Variant, Variant>>.Remove(KeyValuePair<Variant, Variant> item) bool ICollection<KeyValuePair<Variant, Variant>>.Remove(KeyValuePair<Variant, Variant> item)
{ {
ThrowIfReadOnly();
godot_variant variantKey = (godot_variant)item.Key.NativeVar; godot_variant variantKey = (godot_variant)item.Key.NativeVar;
var self = (godot_dictionary)NativeValue; var self = (godot_dictionary)NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
@ -311,7 +339,28 @@ namespace Godot.Collections
} }
} }
bool ICollection<KeyValuePair<Variant, Variant>>.IsReadOnly => false; /// <summary>
/// Returns <see langword="true"/> if the dictionary is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly;
/// <summary>
/// Makes the <see cref="Dictionary"/> read-only, i.e. disabled modying of the
/// dictionary's elements. Does not apply to nested content, e.g. content of
/// nested dictionaries.
/// </summary>
public void MakeReadOnly()
{
if (IsReadOnly)
{
// Avoid interop call when the dictionary is already read-only.
return;
}
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_make_read_only(ref self);
}
/// <summary> /// <summary>
/// Gets the value for the given <paramref name="key"/> in the dictionary. /// Gets the value for the given <paramref name="key"/> in the dictionary.
@ -397,6 +446,14 @@ namespace Godot.Collections
using (str) using (str)
return Marshaling.ConvertStringToManaged(str); return Marshaling.ConvertStringToManaged(str);
} }
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Dictionary instance is read-only.");
}
}
} }
internal interface IGenericGodotDictionary internal interface IGenericGodotDictionary
@ -508,6 +565,9 @@ namespace Godot.Collections
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/> /// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>. /// is <see langword="true"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param> /// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param> /// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary<TKey, TValue> dictionary, bool overwrite = false) public void Merge(Dictionary<TKey, TValue> dictionary, bool overwrite = false)
@ -536,6 +596,13 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Returns the value at the given <paramref name="key"/>. /// Returns the value at the given <paramref name="key"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the dictionary is read-only.
/// </exception>
/// <exception cref="KeyNotFoundException">
/// The property is retrieved and an entry for <paramref name="key"/>
/// does not exist in the dictionary.
/// </exception>
/// <value>The value at the given <paramref name="key"/>.</value> /// <value>The value at the given <paramref name="key"/>.</value>
public TValue this[TKey key] public TValue this[TKey key]
{ {
@ -557,6 +624,8 @@ namespace Godot.Collections
} }
set set
{ {
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key); using var variantKey = VariantUtils.CreateFrom(key);
using var variantValue = VariantUtils.CreateFrom(value); using var variantValue = VariantUtils.CreateFrom(value);
var self = (godot_dictionary)_underlyingDict.NativeValue; var self = (godot_dictionary)_underlyingDict.NativeValue;
@ -616,10 +685,15 @@ namespace Godot.Collections
/// Adds an object <paramref name="value"/> at key <paramref name="key"/> /// Adds an object <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary{TKey, TValue}"/>. /// to this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="key">The key at which to add the object.</param> /// <param name="key">The key at which to add the object.</param>
/// <param name="value">The object to add.</param> /// <param name="value">The object to add.</param>
public void Add(TKey key, TValue value) public void Add(TKey key, TValue value)
{ {
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key); using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue; var self = (godot_dictionary)_underlyingDict.NativeValue;
@ -645,9 +719,14 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key. /// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="key">The key of the element to remove.</param> /// <param name="key">The key of the element to remove.</param>
public bool Remove(TKey key) public bool Remove(TKey key)
{ {
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key); using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue; var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
@ -683,7 +762,21 @@ namespace Godot.Collections
/// <returns>The number of elements.</returns> /// <returns>The number of elements.</returns>
public int Count => _underlyingDict.Count; public int Count => _underlyingDict.Count;
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; /// <summary>
/// Returns <see langword="true"/> if the dictionary is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => _underlyingDict.IsReadOnly;
/// <summary>
/// Makes the <see cref="Dictionary{TKey, TValue}"/> read-only, i.e. disabled
/// modying of the dictionary's elements. Does not apply to nested content,
/// e.g. content of nested dictionaries.
/// </summary>
public void MakeReadOnly()
{
_underlyingDict.MakeReadOnly();
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
=> Add(item.Key, item.Value); => Add(item.Key, item.Value);
@ -691,6 +784,9 @@ namespace Godot.Collections
/// <summary> /// <summary>
/// Clears the dictionary, removing all entries from it. /// Clears the dictionary, removing all entries from it.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
public void Clear() => _underlyingDict.Clear(); public void Clear() => _underlyingDict.Clear();
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
@ -740,6 +836,8 @@ namespace Godot.Collections
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{ {
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(item.Key); using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue; var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
@ -789,5 +887,13 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Dictionary<TKey, TValue>(Variant from) => public static explicit operator Dictionary<TKey, TValue>(Variant from) =>
from.AsGodotDictionary<TKey, TValue>(); from.AsGodotDictionary<TKey, TValue>();
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Dictionary instance is read-only.");
}
}
} }
} }

View file

@ -697,6 +697,9 @@ namespace Godot.NativeInterop
private uint _safeRefCount; private uint _safeRefCount;
public VariantVector _arrayVector; public VariantVector _arrayVector;
private unsafe godot_variant* _readOnly;
// There are more fields here, but we don't care as we never store this in C# // There are more fields here, but we don't care as we never store this in C#
public readonly int Size public readonly int Size
@ -704,6 +707,12 @@ namespace Godot.NativeInterop
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _arrayVector.Size; get => _arrayVector.Size;
} }
public readonly unsafe bool IsReadOnly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _readOnly != null;
}
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -737,6 +746,12 @@ namespace Godot.NativeInterop
get => _p != null ? _p->Size : 0; get => _p != null ? _p->Size : 0;
} }
public readonly unsafe bool IsReadOnly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _p != null && _p->IsReadOnly;
}
public unsafe void Dispose() public unsafe void Dispose()
{ {
if (_p == null) if (_p == null)
@ -766,35 +781,59 @@ namespace Godot.NativeInterop
// A correctly constructed value needs to call the native default constructor to allocate `_p`. // A correctly constructed value needs to call the native default constructor to allocate `_p`.
// Don't pass a C# default constructed `godot_dictionary` to native code, unless it's going to // Don't pass a C# default constructed `godot_dictionary` to native code, unless it's going to
// be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine).
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Explicit)]
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public ref struct godot_dictionary public ref struct godot_dictionary
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly unsafe godot_dictionary* GetUnsafeAddress() internal readonly unsafe godot_dictionary* GetUnsafeAddress()
=> (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _p)); => (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper));
private IntPtr _p; [FieldOffset(0)] private byte _getUnsafeAddressHelper;
public readonly bool IsAllocated [FieldOffset(0)] private unsafe DictionaryPrivate* _p;
[StructLayout(LayoutKind.Sequential)]
private struct DictionaryPrivate
{
private uint _safeRefCount;
private unsafe godot_variant* _readOnly;
// There are more fields here, but we don't care as we never store this in C#
public readonly unsafe bool IsReadOnly
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _p != IntPtr.Zero; get => _readOnly != null;
}
} }
public void Dispose() public readonly unsafe bool IsAllocated
{ {
if (_p == IntPtr.Zero) [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _p != null;
}
public readonly unsafe bool IsReadOnly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _p != null && _p->IsReadOnly;
}
public unsafe void Dispose()
{
if (_p == null)
return; return;
NativeFuncs.godotsharp_dictionary_destroy(ref this); NativeFuncs.godotsharp_dictionary_destroy(ref this);
_p = IntPtr.Zero; _p = null;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
internal struct movable internal struct movable
{ {
private IntPtr _p; private unsafe DictionaryPrivate* _p;
public static unsafe explicit operator movable(in godot_dictionary value) public static unsafe explicit operator movable(in godot_dictionary value)
=> *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value));

View file

@ -376,6 +376,8 @@ namespace Godot.NativeInterop
public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size); public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size);
public static partial void godotsharp_array_make_read_only(ref godot_array p_self);
public static partial void godotsharp_array_shuffle(ref godot_array p_self); public static partial void godotsharp_array_shuffle(ref godot_array p_self);
public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str);
@ -416,6 +418,8 @@ namespace Godot.NativeInterop
public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self, public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self,
in godot_variant p_key); in godot_variant p_key);
public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str); public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str);
// StringExtensions // StringExtensions

View file

@ -1012,6 +1012,10 @@ int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) {
return (int32_t)p_self->resize(p_new_size); return (int32_t)p_self->resize(p_new_size);
} }
void godotsharp_array_make_read_only(Array *p_self) {
p_self->make_read_only();
}
void godotsharp_array_shuffle(Array *p_self) { void godotsharp_array_shuffle(Array *p_self) {
p_self->shuffle(); p_self->shuffle();
} }
@ -1081,6 +1085,10 @@ bool godotsharp_dictionary_remove_key(Dictionary *p_self, const Variant *p_key)
return p_self->erase(*p_key); return p_self->erase(*p_key);
} }
void godotsharp_dictionary_make_read_only(Dictionary *p_self) {
p_self->make_read_only();
}
void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) {
*r_str = Variant(*p_self).operator String(); *r_str = Variant(*p_self).operator String();
} }
@ -1439,6 +1447,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_array_insert, (void *)godotsharp_array_insert,
(void *)godotsharp_array_remove_at, (void *)godotsharp_array_remove_at,
(void *)godotsharp_array_resize, (void *)godotsharp_array_resize,
(void *)godotsharp_array_make_read_only,
(void *)godotsharp_array_shuffle, (void *)godotsharp_array_shuffle,
(void *)godotsharp_array_to_string, (void *)godotsharp_array_to_string,
(void *)godotsharp_dictionary_try_get_value, (void *)godotsharp_dictionary_try_get_value,
@ -1454,6 +1463,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_dictionary_merge, (void *)godotsharp_dictionary_merge,
(void *)godotsharp_dictionary_recursive_equal, (void *)godotsharp_dictionary_recursive_equal,
(void *)godotsharp_dictionary_remove_key, (void *)godotsharp_dictionary_remove_key,
(void *)godotsharp_dictionary_make_read_only,
(void *)godotsharp_dictionary_to_string, (void *)godotsharp_dictionary_to_string,
(void *)godotsharp_string_simplify_path, (void *)godotsharp_string_simplify_path,
(void *)godotsharp_string_to_camel_case, (void *)godotsharp_string_to_camel_case,