Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref structs should not implement interfaces #20226

Closed
VSadov opened this issue Jun 15, 2017 · 14 comments
Closed

ref structs should not implement interfaces #20226

VSadov opened this issue Jun 15, 2017 · 14 comments

Comments

@VSadov
Copy link
Member

VSadov commented Jun 15, 2017

Considering that ref structs cannot be boxed or passed as a generic type argument, implementing interfaces is pointless.

@sharwell
Copy link
Member

What about IDisposable? A using statement requires the type to be IDisposable even if it dispatches the Dispose call to the method on a particular type.

@VSadov
Copy link
Member Author

VSadov commented Jun 15, 2017

Ref struct is not convertible to an interface, even if it claims to be implementing one. In particilar that would not satisfy the requirememts of using.

@KrisVandermotten
Copy link
Contributor

Well, we could change the requirements of using, as in #11420.

VSadov added a commit to VSadov/roslyn that referenced this issue Jun 17, 2017
VSadov added a commit to VSadov/roslyn that referenced this issue Jun 17, 2017
@VSadov
Copy link
Member Author

VSadov commented Aug 10, 2017

fixed in #20293

@nietras
Copy link

nietras commented Jan 7, 2018

It is definitely not pointless, it can used with generics without boxing. Why can it not be used as a generic type argument?

I have a specific need that is blocked by this, namely for my work with Span.Sort?

@nietras
Copy link

nietras commented Jan 7, 2018

I guess it is because there is currently no way to constrain a generic type parameter to avoid possibly boxing it. Why not consider a constraint for this then? Not sure what would be good but:

internal interface ISwapper
{
    void Swap(int i, int j);
}
internal ref struct Swapper<T> : ISwapper // ref structs can't inherit interfaces :( Nor be generic type parameter
{
    Span<T> _items;

    public Swapper(Span<T> items)
    {
        _items = items;
    }

    public void Swap(int i, int j)
    {
        ref T start = ref _items.DangerousGetPinnableReference();
        SpanSortHelper.Swap(ref Unsafe.Add(ref start, i),
                            ref Unsafe.Add(ref start, j));
    }
}

public void SomeMethod<TSwapper>(TSwapper swapper)
    where TSwapper : ref struct 
    // Although it should still work for normal structs, just restrict the availability of boxing etc. 
    // "unboxable" :|
{
     // Swapper can then be used to inject zero, one or more extra items...
}

@jtheisen
Copy link

I second @nietras. This limitation prevents the creation of a lot of very useful building blocks.

@jtheisen
Copy link

As a motivation consider this building block:

	public interface IAction<T>
	{
		void Do(ref T value);
	}

	public static unsafe void ForEach<A, T>(this Memory<T> memory, A action)
		where T : unmanaged
		where A : struct, IAction<T>
	{
		fixed (T *ptr = &memory.Span.GetPinnableReference())
		{
			var p = ptr;
			var e = ptr + memory.Span.Length;
			
			for (; p < e; ++p)
			{
				action.Do(ref *ptr);
			}
		}
	}

And then using this with some implementation:

	public struct PointlesSumAction : IAction<Char>
	{
		public Char sum;

		public void Do(ref Char value)
		{
			sum += value;
		}
	}

...

        {
            var a = new PointlesSumAction();
	    myMemory.ForEach(ref a);
	}

With optimizations turned on, this compiles to a loop with Do inlined, and the range checks being done only once for the ForEach call.

This can be done with Memory now, but it really belongs to Span.

@miyu
Copy link

miyu commented Apr 22, 2021

Please reconsider this. @jtheisen gives a great example that can benefit heavily from inlining. My specific use-case involves traversing a BST with a custom comparer that'd have a ref to a few larger structs (do not want to make class for variety of reasons). My only alternative is to copy-paste my tree search function so that I can benefit from inlining and avoid big struct copies (in my case, my tree has numerous comparers which satisfy the BST invariant. I'm passing struct comparers to benefit from inlining... but probably too much details).

@miyu
Copy link

miyu commented Apr 22, 2021

See also dotnet/csharplang#1148 and dotnet/csharplang#1147

@redknightlois
Copy link

Sorry for reviving a zombie thread but currently hitting the exact same issue. We deal with unmanaged memory A LOT. And the ability to enclose the memory boundaries inside Span<byte> has simplified, and even made our code of complex data structures way faster. If we wouldn't have hit this issue we wouldn't have to rethink the approach. We use struct interface metaprogramming sparingly with excellent results in performance, code readability, and maintainability (we cannot ask for more); the ability to have ref struct implementing interfaces is beyond cosmetic, it allows us to be able to produce very high-performance code without having to jump several hops in coding abstractions to package those Span<byte> into a method (which is also inefficient in comparison).

It is certainly not pointless, the more you use Span<byte> the more important it becomes to have interfaces for ref struct .

@CyrusNajmabadi
Copy link
Member

hi @redknightlois discussions abotu the language go at dotnet/csharplang. See teh linked threads in the post above yours :) this is the repo for the compiler impl.

@jtheisen
Copy link

jtheisen commented Jul 3, 2021

@CyrusNajmabadi Yes, but it would really help a lot if the language implementors could tell them what they need to hear.

Using unboxed structs as interfaces through generics is possible in C# (and probably VB) since the days of Silverlight, yet the OP's statement proves this isn't common knowledge, even among the experts.

And these things are why .NET is so much better than Java, the static language where everything is boxed except ints and floats.

You guys know you need to embrace that advantage, and you did of course, with the introduction of Span&Co - but this right here is an oversight that really needs fixing.

And it's likely easy to fix, so maybe the language implementors should do that first and then just have it sanctioned by the language people, right?

@CyrusNajmabadi
Copy link
Member

And it's likely easy to fix, so maybe the language implementors should do that first and then just have it sanctioned by the language people, right?

No. We don't purposefully deviate in that manner.

Yes, but it would really help a lot if the language implementors could tell them what they need to hear.

It's the same group of people. We just make language changes over at dotnet/csharplang. Then once that is done, the compiler is updated to support the new language version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants