-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
foreach (T value in enumerable) is happy to insert an explicit cast to T from whatever type enumerable actually emits. IIRC this enabled things to work reasonably well back in the pre-generics age so you could foreach (MyClass instance in arrayList) because your ArrayList's enumerator only gave out objects. Otherwise you'd have had to do foreach (object instance in arrayList) { MyClass myClass = (MyClass)instance; ... } all over the place. It was the least awful solution at the time, and also matched Java (iirc).
The thing is, looks like this still happens with foreach (ref T2 value in enumerableOfRefT1s) if enumerableOfRefT1s hands out an enumerator that does not implement IEnumerable<T1> but instead implements the duck-type pattern and has a ref T1 Current property.
I found this the hard way because a certain rendering path in Paint.NET was just not working; you'd draw with the Recolor Tool and it was not working; there was no effect on the canvas.
Turns out, it's because I had this code for the enumerator:
public unsafe readonly ref struct RegionRowPtr<T>
where T : unmanaged
{
private readonly T* ptr;
private readonly int width;
private readonly int y;
...
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator()
{
return new Enumerator(this.ptr, this.width);
}
...
public ref struct Enumerator
//: IEnumerator<T>
{
private T* pCurrent;
private int countLeft;
...
public ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref *this.pCurrent;
}
...
}
}And I was using it this way, elsewhere:
protected override unsafe void OnRenderContent(RegionPtr<ColorBgra> dstContent, Point2Int32 renderOffset)
{
ColorBgra32 fillColor = this.Changes.FillColor;
ColorBgra32 basisColor = this.basisColor;
ThrowIfCancellationRequested();
this.sampleSource.Render(dstContent, renderOffset);
foreach (RegionRowPtr<ColorBgra> dstRow in dstContent.Rows)
{
ThrowIfCancellationRequested();
// NOTE how dstRow's T is ColorBgra, but I'm foreach-ing with ColorBgra32
foreach (ref ColorBgra32 dstColorRef in dstRow)
{
ColorBgra32 dstColor = dstColorRef;
byte b = Int32Util.ClampToByte(dstColor.B + (fillColor.B - basisColor.B));
byte g = Int32Util.ClampToByte(dstColor.G + (fillColor.G - basisColor.G));
byte r = Int32Util.ClampToByte(dstColor.R + (fillColor.R - basisColor.R));
byte a = Int32Util.ClampToByte(dstColor.A + (fillColor.A - basisColor.A));
dstColorRef = ColorBgra32.FromBgra(b, g, r, a);
}
}
}In my code, PaintDotNet.ColorBgra is the "old" color/pixel type, and PaintDotNet.Imaging.ColorBgra32 is the new one, part of the newer more comprehensive imaging framework I've been working on. I'm still converting from the old to the new, little by little, over time. There's an implicit cast from ColorBgra to ColorBgra32 (they are binary compatible). So instead of reading and writing to the refs that the enumerator was handing out, it was casting and then giving me refs to the cast value (presumably on the stack). Reads were fine, writes went nowhere. Changing to foreach (ref ColorBgra fixed it.
I feel like this might be accidental? Surely the compiler should warn about this? I burned an hour on this, and thankfully it's the only location in my entire codebase where the text foreach (ref exists, so it's not a widespread problem, but I could see it causing others a lot of pain and suffering.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status