모든 nullable에 대한 C # 제네릭 형식 제약 조건


111

그래서이 수업이 있습니다 :

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

이제 모든 것을 유형 매개 변수로 사용할 수있는 유형 제약 조건을 찾고 있습니다 null. 즉, 모든 참조 유형과 모든 Nullable( T?) 유형을 의미합니다.

Foo<String> ... = ...
Foo<int?> ... = ...

가능해야합니다.

사용 class유형 제약은 나를 참조 유형을 사용할 수있다.

추가 정보 : 파이프 및 필터 애플리케이션을 작성 중이며 파이프 null라인으로 전달되는 마지막 항목 으로 참조 를 사용하여 모든 필터가 멋지게 종료되고 정리 등을 수행 할 수 있도록 하고 싶습니다 .


1
Nullables 허용하지 않습니다 @ 팀

이 링크가 도움이 될 수 있습니다. social.msdn.microsoft.com/Forums/en-US/…
Réda Mattar

2
이 작업을 직접 수행 할 수 없습니다. 시나리오에 대해 더 자세히 말씀해 주시겠습니까? 아니면 IFoo<T>작업 유형으로 사용하고 팩토리 메서드를 통해 인스턴스를 만들 수 있습니까? 그것은 작동하도록 만들 수 있습니다.
Jon

왜 이런 식으로 무언가를 제한해야하는지 잘 모르겠습니다. "if x == null"을 if x.IsNull () "로 바꾸는 것이 유일한 의도라면 이전 구문에 익숙한 개발자 99.99 %에게는 무의미하고 직관적이지 않은 것처럼 보입니다. 컴파일러는 그렇게 할 수 없습니다." 만약 당신이 이미 포함하고 있으므로 어쨌든 (INT) × == 널 (null) ".
RJ 로한

답변:


22

컴파일 타임 검사를하지 않고 Foo의 생성자에서 런타임 검사를 수행하려는 경우 유형이 참조 또는 nullable 유형이 아닌지 확인하고이 경우 예외를 throw 할 수 있습니다.

런타임 확인 만 허용되지 않을 수 있지만 다음과 같은 경우를 대비하여 알고 있습니다.

public class Foo<T>
{
    private T item;

    public Foo()
    {
        var type = typeof(T);

        if (Nullable.GetUnderlyingType(type) != null)
            return;

        if (type.IsClass)
            return;

        throw new InvalidOperationException("Type is not nullable or reference type.");
    }

    public bool IsNull()
    {
        return item == null;
    }
}

그런 다음 다음 코드가 컴파일되지만 마지막 코드 ( foo3)는 생성자에서 예외를 발생시킵니다.

var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());

var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());

var foo3= new Foo<int>();  // THROWS
Console.WriteLine(foo3.IsNull());

31
이 작업을 수행 위하여려고하는 경우에, 당신은에서 확인 할 수 있는지 확인 정적 그렇지 않으면 당신은 당신의 제네릭 클래스의 모든 인스턴스의 건설을 늦추고있을거야, 생성자 (불필요)
에이 몬 Nerbonne

2
당신은 정적 생성자에서 예외를 발생하지 않아야 @EamonNerbonne : msdn.microsoft.com/en-us/library/bb386039.aspx
매튜 왓슨

5
지침은 절대적인 것이 아닙니다. 이 검사를 원하면 런타임 검사 비용과 정적 생성자에서 예외의 불편 함을 절충해야합니다. 여기에서 가난한 사람의 정적 분석기를 실제로 구현하고 있기 때문에이 예외는 개발 중을 제외하고는 절대로 던지지 않아야합니다. 마지막으로 (현명하지 않은) 어떤 희생을 치르더라도 정적 생성 예외를 피하려는 경우에도 인스턴스 생성자에서 가능한 한 많은 작업을 정적으로 수행해야합니다 (예 : "isBorked"플래그 설정 등).
에이 몬 Nerbonne

덧붙여서, 나는 당신이 이것을 전혀 시도해서는 안된다고 생각합니다. 대부분의 상황에서 저는 누수되고 실패하기 쉬운 추상화를 시도하고 작업하는 것보다 이것을 C # 제한으로 받아들이는 것을 선호합니다. 예를 들어 다른 솔루션은 클래스 만 요구하거나 구조체 만 요구하거나 (그리고 명시 적으로 em nullable로 설정) 두 가지를 모두 수행하고 두 버전을 갖는 것입니다. 이것은이 솔루션에 대한 비판이 아닙니다. 이 문제는 잘 해결 될 수 없다는 것입니다. 즉, 사용자 지정 roslyn 분석기를 기꺼이 작성하지 않는 한.
에이 몬 Nerbonne

1
두 가지 장점을 모두 얻을 수 있습니다 static bool isValidType. 정적 생성자에서 설정 한 필드를 유지 한 다음 인스턴스 생성자에서 해당 플래그를 확인하고 유효하지 않은 유형이면 던지기 만하면 구성 할 때마다 모든 검사 작업을 수행하지 않습니다. 인스턴스. 이 패턴을 자주 사용합니다.
Mike Marynowski

20

제네릭 에서 OR 와 동등한 것을 구현하는 방법을 모르겠습니다 . 그러나 nullable 형식에 대해서는 null을 만들고 구조에 대해서는 0 값을 만들기 위해 기본 키워드 를 사용하도록 제안 할 수 있습니다 .

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

Nullable 버전을 구현할 수도 있습니다.

class MyNullable<T> where T : struct
{
    public T Value { get; set; }

    public static implicit operator T(MyNullable<T> value)
    {
        return value != null ? value.Value : default(T);
    }

    public static implicit operator MyNullable<T>(T value)
    {
        return new MyNullable<T> { Value = value };
    }
}

class Foo<T> where T : class
{
    public T Item { get; set; }

    public bool IsNull()
    {
        return Item == null;
    }
}

예:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
        Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
        Console.WriteLine(new Foo<object>().IsNull()); // true
        Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false

        var foo5 = new Foo<MyNullable<int>>();
        int integer = foo5.Item;
        Console.WriteLine(integer); // 0

        var foo6 = new Foo<MyNullable<double>>();
        double real = foo6.Item;
        Console.WriteLine(real); // 0

        var foo7 = new Foo<MyNullable<double>>();
        foo7.Item = null;
        Console.WriteLine(foo7.Item); // 0
        Console.WriteLine(foo7.IsNull()); // true
        foo7.Item = 3.5;
        Console.WriteLine(foo7.Item); // 3.5
        Console.WriteLine(foo7.IsNull()); // false

        // var foo5 = new Foo<int>(); // Not compile
    }
}

프레임 워크의 원래 Nullable <T>는 클래스가 아니라 구조체입니다. 값 유형을 모방하는 참조 유형 래퍼를 만드는 것은 좋은 생각이 아니라고 생각합니다.
Niall Connaughton

1
기본값 을 사용한 첫 번째 제안 은 완벽합니다! 이제 제네릭 형식이 반환되는 템플릿은 개체에 대해 null을 반환하고 기본 제공 형식에 대해 기본값을 반환 할 수 있습니다.
Casey Anderson

13

"nullable"(참조 유형 또는 Nullables)을 취할 수있는 일반적인 정적 메서드를 원하는 더 간단한 경우에 대해이 문제에 직면하여 만족스러운 솔루션없이이 질문을 받았습니다. 그래서 나는 두 개의 오버로드 된 메서드, 즉 a T및 제약 조건이 where T : class있고 다른 하나는 a T?where T : struct.

그런 다음 해당 솔루션을이 문제에 적용하여 생성자를 비공개 (또는 보호)하고 정적 팩토리 메서드를 사용하여 컴파일 타임에 확인할 수있는 솔루션을 만들 수도 있다는 것을 깨달았습니다.

    //this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000)
    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return Foo<TFoo>.Create(value);
        }

        public static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return Foo<TFoo?>.Create(value);
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo(T value)
        {
            item = value;
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return new Foo<TFoo>(value);
        }

        internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return new Foo<TFoo?>(value);
        }
    }

이제 다음과 같이 사용할 수 있습니다.

        var foo1 = new Foo<int>(1); //does not compile
        var foo2 = Foo.Create(2); //does not compile
        var foo3 = Foo.Create(""); //compiles
        var foo4 = Foo.Create(new object()); //compiles
        var foo5 = Foo.Create((int?)5); //compiles

매개 변수가없는 생성자를 원하면 오버로딩이 좋지는 않지만 다음과 같이 할 수 있습니다.

    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return Foo<TFoo>.Create<TFoo>();
        }

        public static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return Foo<TFoo?>.CreateNullable<TFoo>();
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo()
        {
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return new Foo<TFoo>();
        }

        internal static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return new Foo<TFoo?>();
        }
    }

다음과 같이 사용하십시오.

        var foo1 = new Foo<int>(); //does not compile
        var foo2 = Foo.Create<int>(); //does not compile
        var foo3 = Foo.Create<string>(); //compiles
        var foo4 = Foo.Create<object>(); //compiles
        var foo5 = Foo.CreateNullable<int>(); //compiles

이 솔루션에는 몇 가지 단점이 있습니다. 하나는 객체를 구성하는 데 'new'를 사용하는 것을 선호 할 수 있다는 것입니다. 다른 하나는 Foo<T>다음과 같은 유형 제약 조건에 대한 일반 유형 인수 로 사용할 수 없다는 것 where TFoo: new()입니다. 마지막으로 여기에 필요한 추가 코드는 특히 여러 개의 오버로드 된 생성자가 필요한 경우 증가합니다.


8

언급했듯이 컴파일 시간을 확인할 수 없습니다. .NET의 일반 제약 조건은 심각하게 부족하며 대부분의 시나리오를 지원하지 않습니다.

그러나 나는 이것이 런타임 검사를위한 더 나은 솔루션이라고 생각합니다. 둘 다 상수이기 때문에 JIT 컴파일 시간에 최적화 할 수 있습니다.

public class SomeClass<T>
{
    public SomeClass()
    {
        // JIT-compile time check, so it doesn't even have to evaluate.
        if (default(T) != null)
            throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");

        T variable;
        // This still won't compile
        // variable = null;
        // but because you know it's a nullable type, this works just fine
        variable = default(T);
    }
}

3

이러한 유형 제약은 불가능합니다. 형식 제약 에 대한 문서에 따르면 nullable 및 참조 형식을 모두 캡처하는 제약 조건이 없습니다. 제약은 결합으로 만 결합 될 수 있기 때문에 이러한 제약을 조합으로 생성 할 수있는 방법은 없습니다.

그러나 항상 == null을 확인할 수 있으므로 필요에 따라 unconstraint 형식 매개 변수로 대체 할 수 있습니다. 유형이 값 유형이면 검사는 항상 거짓으로 평가됩니다. 그런 다음 의미 체계가 적합한 한 R # 경고 "값 유형을 null과 비교할 수 있음"이 표시 될 수 있습니다. 이는 중요하지 않습니다.

대안은 다음을 사용할 수 있습니다.

object.Equals(value, default(T))

null 검사 대신 default (T) 여기서 T : class는 항상 null입니다. 그러나 이는 nullable이 아닌 값이 명시 적으로 설정되지 않았거나 기본값으로 설정된 경우 날씨를 구분할 수 없음을 의미합니다.


문제는 값이 설정되지 않았는지 확인하는 방법이라고 생각합니다. null과 다른 점은 값이 초기화되었음을 나타냅니다.
Ryszard Dżegan 2013

값 유형이 항상 설정되기 때문에 접근 방식이 무효화되지는 않습니다 (적어도 해당 기본값에 대해 암시 적으로).
스벤 AMANN

3

나는 사용한다

public class Foo<T> where T: struct
{
    private T? item;
}

-2
    public class Foo<T>
    {
        private T item;

        public Foo(T item)
        {
            this.item = item;
        }

        public bool IsNull()
        {
            return object.Equals(item, null);
        }
    }

    var fooStruct = new Foo<int?>(3);
        var b = fooStruct.IsNull();

        var fooStruct1 = new Foo<int>(3);
        b = fooStruct1.IsNull();

        var fooStruct2 = new Foo<int?>(null);
        b = fooStruct2.IsNull();

        var fooStruct3 = new Foo<string>("qqq");
        b = fooStruct3.IsNull();

        var fooStruct4 = new Foo<string>(null);
        b = fooStruct4.IsNull();

이 타이핑을 통해 new Foo <int> (42) 및 IsNull ()은 false를 반환합니다. 이는 의미 적으로는 정확하지만 특별히 의미가 없습니다.
RJ Lohan 2013

1
42 번은 "생명, 우주, 모든 것에 대한 궁극적 인 질문에 대한 답"입니다. 간단히 말해서, 모든 int 값에 대해 IsNull은 false를 반환합니다 (0 값에 대해서도).
Ryszard Dżegan 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.