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

where not condition in generic type constraint #3560

Closed
electrophere opened this issue Jun 11, 2020 · 16 comments
Closed

where not condition in generic type constraint #3560

electrophere opened this issue Jun 11, 2020 · 16 comments

Comments

@electrophere
Copy link

hi can i have "not Condition" in generic types
i mean

        public interface ISrotedArray: IArray
        {

        }
        public interface IArray
        {

        }
        public static void insert<Input>(this Input I)
            where Input :  IArray
            where Input : !ISrotedArray
        {
            // Insert At End Of Array
        }
        public static void insert<Input>(this Input I)
            where Input : IArray
            where Input : ISrotedArray
        {
            // Insert At Binary Position Of Array
        }
@HaloFour
Copy link
Contributor

Generic parameter constraints are not a part of a method signature and cannot be used to overload a method, so this type of solution wouldn't work even if a "not" constraint existed.

@HaloFour
Copy link
Contributor

No, I mean that the runtime doesn't allow declaring those two methods because they have identical signatures, which is static void insert<Input>(Input I).

The appropriate way to handle this today is to declare a single method and have it do a type check:

        public static void insert<Input>(this Input I)
            where Input :  IArray
        {
            if (I is ISrotedArray) {
                 // handle a sorted array here
            }
            else {
                 // handle an unsorted array here
            }
        }

@electrophere
Copy link
Author

electrophere commented Jun 11, 2020

@HaloFour can we have something like blow for expression?

        public InterfaceGroup IOptions:
        IOption_1,IOption_2,IOption_3,IOption_4,IOption_5,IOption_6,IOption_7
        { }
        public interface IOption_1 { }
        public interface IOption_2 { }
        public interface IOption_3 { }
        public interface IOption_4 { }
        public interface IOption_5 { }
        public interface IOption_6 { }
        public interface IOption_7 { }

        public static void OptionDo<Input>(this Input I)
            where Input :  IOptions
            where Input : !IOption2
        {
        }

@electrophere
Copy link
Author

electrophere commented Jun 11, 2020

Or alternative of

        public InterfaceGroup IOptions:
            IOption_1,IOption_2,IOption_3,IOption_4,IOption_5,IOption_6,IOption_7
        { }
        public interface IOption_1 { }
        public interface IOption_2 { }
        public interface IOption_3 { }
        public interface IOption_4 { }
        public interface IOption_5 { }
        public interface IOption_6 { }
        public interface IOption_7 { }

        public Result Put_Option_No5<Input,Result>(this Input I)
            where Input : IOption_1, IOption_2, IOption_3, IOption_4, IOption_5, IOption_6, IOption_7
            where Result : IOption_1, IOption_2, IOption_3, IOption_4, IOption_6, IOption_7
        {
            return I;
        }
        public Result V2_Put_Option_No5<Input, Result>(this Input I)
            where Input : IOptions
            where Result : IOptions, !IOption5
        {
            return I;
        }
        public Result V3_Put_Option_No5<Input, Result>(this Input I)
            where Input : IOptions
            where Result : IOptions - IOption5
        {
            return I;
        }

@CyrusNajmabadi
Copy link
Member

@HaloFour can we have something like blow for expression?

Not without a very compelling use case :)

@Thaina
Copy link

Thaina commented Jun 12, 2020

As for me, sometimes I want this feature for convert function. Such as convert to string that can take anything but object and string, for example

@electrophere
Copy link
Author

@CyrusNajmabadi very compelling use case? :|
alright lets talk about System.IO.Stream class for instance, when we have a stream instance then we don't know what is that supporting until runtime, we don't know is that supporting seeking or not, we don't know is that readable or writable and so on, you may answer we can check that in runtime and then get decision, you are right but this just confusing developers in code time
let me tell an example

//Error CS0220  The operation overflows at compile time 
int a = int.MaxValue + 1;

so why c# get me error in code time? i can get an exception in runtime, but answer is c# trying to stop confusing us and also make debug codes faster, so you can find this error in code time and must get decision right there
but in blow example c# don't get us exception because c# don't know what is value of "i" in runtime

        public static void Add(int i)
        {
            //no error in code time 
            int a = int.MaxValue + i;
        }

sorry about simple samples but you need to very compelling use case and what i talking is very very basic
lets back to stream class again

    public interface IStream
    {
        Stream Stream { get; }
    }
    public interface IReadableStream: IStream { }
    public interface IWritableStream: IStream { }
    public interface ISeekableStream: IStream { }

i recommend something like blow just for better debugging and make clear rule for stop confusing developers

    public static class StreamEx 
    {
        public static int ReadFrom<StreamType>(
            this StreamType IStream,
            int Position, byte[] Buffer, int offset, int count)
            where StreamType:IReadableStream, ISeekableStream
        {
            if (IStream.Stream.CanSeek==false)
                throw new AccessViolationException("Stream is not Seekable");
            IStream.Stream.Seek(Position, SeekOrigin.Begin);
            return IStream.Stream.Read(Buffer, offset, count);
        }
    }

so you may ask where is "the not condition" we using, there :

        public static MS MakeSeekableStream<StreamType>(
            this StreamType IStream,
            int Position, byte[] Buffer, int offset, int count)
            where StreamType: IReadableStream, !ISeekableStream ////<<< there
        {
            if (IStream.Stream.CanSeek==true)
                throw new AccessViolationException("Stream is Seekable");
            var MyMs = new MemoryStream();
            IStream.Stream.CopyTo(MyMs);
            return new MS() { Stream = MyMs };
        }
        public class MS : IReadableStream, IWritableStream, ISeekableStream
        {
            public Stream Stream { get; set; }
        }

so developer can known what is stream type in code time not only at runtime
and this is just very very very simple sample of how usable is the idea
i think this make libraries more understandable for other developers
my other criticism is why we have implicit exception in blow

    public interface IStream
    {
        Stream Stream { get; }
    }
    public interface IReadableStream: IStream { }
    public interface IWritableStream: IStream { }
    public interface ISeekableStream: IStream { }
    public static class StreamEx 
    {
        public static ResultType PutSeekOptionOnlyForCodeTime<StreamType,ResultType>(
            this StreamType IStream)
            where StreamType: IReadableStream, ISeekableStream
            where ResultType: IReadableStream, IWritableStream, ISeekableStream
        {
            // Error CS0029  Cannot implicitly convert type 'StreamType' to 'ResultType'
            return IStream;
        }
    }

i know how can i handle this in c# but what i don't know is how can i put every interface from anonymous types

    public interface IStream
    {
        Stream Stream { get; }
    }
    public interface IReadableStream: IStream { }
    public interface IWritableStream: IStream { }
    public interface ISeekableStream: IStream { }
    public static class StreamEx 
    {
        public static ResultType PutSeekOptionOnlyForCodeTime<StreamType,ResultType>(
            this StreamType IStream)
            where StreamType: ISeekableStream
            where ResultType: AllInterfaces(StreamType) , !ISeekableStream
        {
            // this just trying to remove one interface, so why always i must make new class for just very simple action?
            return IStream;
        }
    }

@electrophere
Copy link
Author

@HaloFour static void insert<Input>(Input I)
i know about signatures
why c# don't make internal function for this like what made in lambada expressions? Sample sharplab.io

        static void Main(string[] args)
        {
            var value =25;
            Action MyAc = () => Console.WriteLine(value); // there made a new hidden private class
         }

my codes was:

    public static class Ex
    {
        public interface ISrotedArray : IArray
        {

        }
        public interface IArray
        {

        }
        public static void insert<Input>(this Input I)
            where Input : IArray
        {
            // Insert At End Of Array
        }
       //Error CS0111  Type 'Ex' already defines a member called 'insert' with the same parameter types
      // V V V
        public static void insert<Input>(this Input I)
            where Input : IArray,ISrotedArray
        {
            // Insert At Binary Position Of Array
        }
    }

So compiler can translate to

    public static class Ex
    {
        public interface ISrotedArray : IArray
        {

        }
        public interface IArray
        {

        }
        public static void insert<Input>(this Input I)
            where Input :  IArray
        {
            if (I is ISrotedArray) {
                 insert_Type2(I);
            }
            else {
                insert_Type1(I);
            }
        }
        [CompilerGenerated]
        private static void insert_Type1<Input>(this Input I)
            where Input : IArray
        {
            // Insert At End Of Array
        }
        [CompilerGenerated]
        private static void insert_Type2<Input>(Input I)
            where Input : IArray,ISrotedArray
        {
            // Insert At Binary Position Of Array
        }
    }

@HaloFour
Copy link
Contributor

@electrophere

why c# don't make internal function for this like what made in lambada expressions?

Because you're declaring two public functions. The C# compiler does not reinterpret the layout of your classes, it assumes that you know what you're doing.

Something similar to this was discussed with overload via pattern matching and that was declined because C# overloading is resolved entirely at compile time whereas this would be runtime resolution.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 13, 2020

Duplicate of #707.

@electrophere
Copy link
Author

@Joe4evr i don't talking about overloads, that code isn't implicit and haven't ambiguous method call
let me describe

static class ex1
{
  public static void AsSomething<T>(this IEnumerable<IEnumerable<T>> arg) { }
}
static class ex2
{
  public static void AsSomething<T>(this IEnumerable<T> arg) {}
}
class Program
{
        static void Run<T>(IEnumerable<IEnumerable<T>> IE)
        {
            IE.AsSomething(); // <<< there is no error such like ambiguous method call
        }
}

i think that method should be use in same class but i talking about blow

    public interface ISrotedArray : IArray { }
    public interface IArray { }
    static class ex1
    {
        public static void insert<Input>(this Input I)
        where Input : IArray
        {
            // Insert At End Of Array
        }
    }
    static class ex2
    {
        public static void insert<Input>(this Input I)
            where Input : IArray,ISrotedArray
        {
            // Insert At Binary Position Of Array
        }
    }
    class Program
    {
        static void Run<T>(ISrotedArray Ar)
        {
             //Error:The call is ambiguous between the following methods or properties: 
            //'ex1.insert<Input>(Input)' and 'ex2.insert<Input>(Input)'
            Ar.insert(); // <<< ambiguous call Error
        }
    }

@electrophere
Copy link
Author

electrophere commented Jun 13, 2020

@HaloFour The C# compiler does not reinterpret the layout of your classes, it assumes that you know what you're doing.

yes you right but c# also trying to make machine language close to humans logics
we can also write byte code directly :| by assumes that we know what we're doing

@electrophere
Copy link
Author

@HaloFour i have idea, i hope that be in Champion "Improved overload candidates"
what if we have more explicit on C# overload selector
which must select that one of overloads which is more close to my object details

    public interface ISrotedArray : IArray { }
    public interface IArray { }
    static class ex1
    {
        public static void insert<Input>(this Input I)
        where Input : IArray
        {
            // Insert At End Of Array
        }
    }
    static class ex2
    {
        public static void insert<Input>(this Input I)
            where Input : IArray,ISrotedArray
        {
            // Insert At Binary Position Of Array
        }
    }
    class Program
    {
        static void RunAsSorted<T>(ISrotedArray Ar)
        {
            Ar.insert(); // <<< must select "ex2.insert<Input>"
        }
        static void RunAsNotSorted<T>(IArray Ar)
        {
            Ar.insert(); // <<< must select "ex1.insert<Input>"
        }
    }

@HaloFour
Copy link
Contributor

@electrophere

but c# also trying to make machine language close to humans logics

I don't know what that means, but that doesn't sound anything like what C# is trying to do.

what if we have more explicit on C# overload selector

Your example is still ambiguous, ex1.insert and ex2.insert both apply to ISortedArray. Generic constraints can remove an overload from contention, but the compiler is not going to try to figure out which of those remaining overloads is somehow "better". I'd suggest not abusing overloading like this.

@333fred
Copy link
Member

333fred commented Jun 13, 2020

This does seem like a duplicate of #707. Closing as such.

@333fred 333fred closed this as completed Jun 13, 2020
@electrophere
Copy link
Author

electrophere commented Jun 14, 2020

@HaloFour Your example is still ambiguous, ex1.insert and ex2.insert both apply to ISortedArray

i don't know what in my examples is understandable
let me tell an example in real world, i told to my friend to shop a sandwich for me and i also told if that sandwich have extra cheese its better
when he returned he tell to me they have two type of sandwich and one of that has extra cheese and other has little cheese, i was ambiguous and come back with empty hands (that's really happened :D )

in other hand c# have a bug here because

where Input : IArray,ISrotedArray

that one can't never select ever as extension method, so why i wrote that and why c# accepted that as extension in same namespace?
so why c# let me use "this" keyword for other one?
convince me, how can you select that just via extension method and not directly?

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

No branches or pull requests

6 participants