Nullable i operatory?

0

proste pytanie na przykladzie:
int? a = 1;
int? b = 2;
int? z = null;

z = a + b; // wynik 3, a nie ma rzutowan na int

jak to jest ze a.GetType() mowi ze jest to int (System.Int32) - w runtime

wszystko wskazuje na to ze w runtime int? jest widziane jako int (jesli ma wartosc), a nie jako Nullable<int>

czy mozliwe jest zaimplementowanie wlasnej struktury generycznej (jak Nullable<T>) zeby zachowanie jesli chodzi o stosowanie operatorow bylo podobne, czyli jesli uzyje +, to uzyty zostanie operator z typu T

0

To nic skomplikowanego. Wystarczy nadpisać metodę GetType() używając new (nie jest to metoda wirtualna, więc nie można użyć override) we własnej klasie i zwrócić typ parametru generycznego klasy. A druga sprawa, żeby dodawać niezależnie od użytego typu generycznego (czyli wykorzystać sumowanie zdefiniowane dla użytego typu generycznego), wystarczy zdefiniować operator typu implicit dla konwersji z JakasKlasa<T> do T. Tak więc jeśli CLR nie znajdzie w Nullable<T> zdefiniowanej metody sumowania, to próbuje sumować na typie uzyskanym z konwersji.

Korzystając z kodu Mono, przy małych przeróbkach, potrzebnych żeby działało tak samo jak typ Nullable<T> z MS .NET (szczegóły w komentarzach), wygląda to mniej więcej tak:

public static class MyNullable
	{
		public static int Compare<T>(MyNullable<T> left, MyNullable<T> right) where T : struct
		{
			IComparable icomparable = left.value as IComparable;
			if (icomparable == null)
				throw new ArgumentException("At least one object must implement IComparable.");
			if (left.has_value == false && right.has_value == false)
				return 0;
			if (!left.has_value)
				return -1;
			if (!right.has_value)
				return 1;

			return icomparable.CompareTo(right.value);
		}

		public static bool Equals<T>(MyNullable<T> value1, MyNullable<T> value2) where T : struct
		{
			return value1.Equals(value2);
		}

		public static Type GetUnderlyingType(Type nullableType)
		{
			if (nullableType == null)
				throw new ArgumentNullException("nullableType");
			if (nullableType.IsGenericType && nullableType.GetGenericTypeDefinition() == typeof(MyNullable<>))
				return nullableType.GetGenericArguments()[0];
			else
				return null;
		}
	}

	[Serializable]
	public struct MyNullable<T> where T : struct
	{
		#region Sync with runtime code
		internal T value;
		internal bool has_value;
		#endregion

		public MyNullable(T value)
		{
			this.has_value = true;
			this.value = value;
		}

		public bool HasValue
		{
			get { return has_value; }
		}

		public T Value
		{
			get
			{
				if (!has_value)
					throw new InvalidOperationException("Nullable object must have a value.");

				return value;
			}
		}

		public override bool Equals(object other)
		{
			if (other == null)
				return has_value == false;
			if (!(other is MyNullable<T>))
				return false;

			return Equals((MyNullable<T>)other);
		}

		bool Equals(MyNullable<T> other)
		{
			MyNullable<T> no = (MyNullable<T>)other;
			if (no.has_value != has_value)
				return false;

			if (has_value == false)
				return true;

			return no.value.Equals(value);
		}

		public override int GetHashCode()
		{
			if (!has_value)
				return 0;

			return value.GetHashCode();
		}

		public T GetValueOrDefault()
		{
			return GetValueOrDefault(default(T));
		}

		public T GetValueOrDefault(T def_value)
		{
			if (!has_value)
				return def_value;
			else
				return value;
		}

		public override string ToString()
		{
			if (has_value)
				return value.ToString();
			else
				return String.Empty;
		}

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

		public static implicit operator T(MyNullable<T> value) // <- W mono jest 'explicit', ale wtedy działa inaczej niż w MS .NET
		{
			return value.Value;
		}

		// A to wystarczy dodać żeby zwracało typ parametru generycznego
		public new Type GetType()
		{
			return typeof(T);
		}

		// These are called by the JIT
		// Ironicly, the C#  code is the same for these two,
		// however, on the inside they do somewhat different things
		static object Box(T? o)
		{
			if (o == null)
				return null;
			return (T)o;
		}

		static T? Unbox(object o)
		{
			if (o == null)
				return null;
			return (T)o;
		}
	}
0

Chyba można trzymać obiekt typu T jako pole klasy generycznej i przeciążyć operator dodawania tak, aby działał na wartości tego pola.

//up był szybszy ;)

0

@somekind, operatora dodawania nie przeciążysz tak łatwo dla klasy generycznej, bo nie znasz typu. A nie każdy typ obsługuje dodawanie. Więc dla klas generycznych najlepiej takie operacje powierzać typowi generycznemu właśnie poprzez konwersję typu implicit (ewentualnie explicit). Jeśli wybrany typ generyczny nie będzie obsługiwał danego operatora, to kompilator poinformuje o błędzie.

0

Guifiak - chyba lepiej dać explicit na powrót. Nawet w MS .NET jest:

   public static implicit operator T?(T value)
    {
        return new T?(value);
    }

    public static explicit operator T(T? value)
    {
        return value.Value;
    }

Ma to oczywisty sens - nie każdy Nullable<T> da się przedstawić jako T, ale każdy T da się skonwertować na Nullable<T>.

Gufiak - widzę Twoje posty od niedawna i musze stwierdzić, że ... masz imponującą wiedzę o .NET. Myślałeś nad zarejestrowaniem się do serwisu .. i napisaniem kilku artykułów? :> Cenna wiedza mile jest tu widziana.

0
gufiak napisał(a)

@somekind, operatora dodawania nie przeciążysz tak łatwo dla klasy generycznej, bo nie znasz typu. A nie każdy typ obsługuje dodawanie.

Daleko idący wniosek biorąc pod uwagę, że nie napisano o której wersji języka mówimy ;)

class GenClass<T> where T: struct, IConvertible, IComparable, IFormattable
{
    private T value;

    public GenClass(T t)
    {
        this.value = t;
    }

    public static GenClass<T> operator +(GenClass<T> a, GenClass<T> b)
    {
        return new GenClass<T>((dynamic)a.value + (dynamic)b.value);
    }

    public override string ToString()
    {
        return this.value.ToString();
    }
}

Natomiast w 2.0 dużo trudniej nie będzie (za to pewno mniej elastycznie, chociaż gdyby dać double zamiast int do obliczeń, to pewno nie będzie się wywalało):

class GenClass<T> where T: struct, IConvertible, IComparable, IFormattable
{
    private T value;

    public GenClass(T t)
    {
        this.value = t;
    }

    public static GenClass<T> operator +(GenClass<T> a, GenClass<T> b)
    {
        int ia = Convert.ToInt32(a.value);
        int ib = Convert.ToInt32(b.value);
        int sum = ia + ib;
        return new GenClass<T>((T)(IConvertible)sum);
    }

    public override string ToString()
    {
        return this.value.ToString();
    }
}
0

@somekind, chciałbym zaznaczyć, że napisałem:

operatora dodawania nie przeciążysz tak łatwo dla klasy generycznej
Napisałem tak dlatego, że jestem świadomy, iż nie jest to nie możliwe, ale jest to niekoniecznie dobre rozwiązanie. Konwersja do int to półśrodek gdyż, po pierwsze, nie wszystko zmieści się w int-cie, a po drugie, nie wszystko skonwertujesz do int-a, chociażby zwykły string się tutaj wyłoży. A jeśli dobrze zrozumiałem massther-a, to nie pyta jak zrobić, żeby '+' działał dla typów numerycznych, a nawet dla typów prostych, a co więcej, nawet nie chodzi o samo Nullable<T>. Wersja z typem dynamicznym, to już zupełnie inna bajka. Ale niestety, to dopiero C# 4.0, więc na razie powinniśmy traktować to czysto teoretycznie. Tym bardziej, że Nullable<T> sumuje bez problemów w starszych wersjach C#.

I tu właśnie jest zagadka, bo zgadzam się z Deti, w kodzie MS .NET faktycznie jest explicit i nie mogę znaleźć przyczyny faktu, że dodawanie działa dla typów nullable, a po użyciu kodu klasy Nullable z Nullable.cs MS .NET, tylko ze zmienioną nazwą typu, już takie działanie jest przez kompilator zabronione. Ma ktoś jakieś pomysły? Bo nie podejrzewam, żeby kompilator, czy CLR jakoś specjalnie traktował typ Nullable... Zauważcie również, że możliwe jest przypisanie

int? z = null;

chociaż kod dostępny w pliku Nullable.cs na to nie pozwala. Więc ciekaw jestem jaki mechanizm umożliwia takie operacje. Jeśli coś wiecie, chętnie się dowiem. Nieraz coś takiego by się przydało.

Ale tu też drobna uwaga, zauważyłem, że dodawanie liczb typu Nullable<T> jest mało wydajne. Tak wygląda dodawanie typów int w assemblerze:

				int e = c + d;
000000e3  mov         eax,dword ptr [ebp-20h] 
000000e6  add         eax,dword ptr [ebp-24h] 
000000e9  mov         dword ptr [ebp-28h],eax 

A tak sumowanie typów Nullable<int>:

				R = A + B;
000000f8  lea         edi,[ebp-3Ch] 
000000fb  lea         esi,[ebp-2Ch] 
000000fe  movq        xmm0,mmword ptr [esi] 
00000102  movq        mmword ptr [edi],xmm0 
00000106  lea         edi,[ebp-44h] 
00000109  lea         esi,[ebp-34h] 
0000010c  movq        xmm0,mmword ptr [esi] 
00000110  movq        mmword ptr [edi],xmm0 
00000114  lea         ecx,[ebp-3Ch] 
00000117  call        69F2FBB8 
0000011c  mov         dword ptr [ebp-6Ch],eax 
0000011f  lea         ecx,[ebp-44h] 
00000122  call        69F2FBB8 
00000127  mov         dword ptr [ebp-70h],eax 
0000012a  mov         eax,dword ptr [ebp-6Ch] 
0000012d  and         eax,dword ptr [ebp-70h] 
00000130  jne         00000138 
00000132  nop              
00000133  jmp         000001D4 
00000138  lea         ecx,[ebp-3Ch] 
0000013b  call        69F2FAE8 
00000140  mov         dword ptr [ebp-74h],eax 
00000143  lea         ecx,[ebp-44h] 
00000146  call        69F2FAE8 
0000014b  mov         dword ptr [ebp-78h],eax 
0000014e  lea         edi,[ebp-80h] 
00000151  pxor        xmm0,xmm0 
00000155  movq        mmword ptr [edi],xmm0 
00000159  lea         ecx,[ebp-80h] 
0000015c  mov         edx,dword ptr [ebp-74h] 
0000015f  add         edx,dword ptr [ebp-78h] 
00000162  call        69F2FB58 

Jest sporo dłuższe od dodawania własnego typu Nullable, gdzie użyłem rzutowania implicit:

				MyNullable<int> r = a + b;
00000090  lea         eax,[ebp-14h] 
00000093  sub         esp,8 
00000096  movq        xmm0,mmword ptr [eax] 
0000009a  movq        mmword ptr [esp],xmm0 
0000009f  call        dword ptr ds:[03C30524h] 
000000a5  mov         dword ptr [ebp-5Ch],eax 
000000a8  lea         eax,[ebp-1Ch] 
000000ab  sub         esp,8 
000000ae  movq        xmm0,mmword ptr [eax] 
000000b2  movq        mmword ptr [esp],xmm0 
000000b7  call        dword ptr ds:[03C30524h] 
000000bd  mov         dword ptr [ebp-60h],eax 
000000c0  lea         ecx,[ebp-68h] 
000000c3  mov         edx,dword ptr [ebp-5Ch] 
000000c6  add         edx,dword ptr [ebp-60h] 
000000c9  call        dword ptr ds:[03C30520h] 

Dlatego raczej nie opłaca się operować na typie Nullable tylko od razu po sprawdzeniu, czy wartość nie jest null, rzutować do wewnętrznego typu.

Ma to oczywisty sens - nie każdy Nullable<T> da się przedstawić jako T, ale każdy T da się skonwertować na Nullable<T>.
Oczywiście, ale żeby zaoszczędzić sobie niepotrzebnego jawnego rzutowania, śmiało można użyć implicit. Jest to po prostu wygodne. A jak będziemy chcieli rzutować do typu, do którego nie da się rzutować, to kompilator grzecznie nas o tym poinformuje. Niemniej jednak, zgodnie z zasadami przyjętymi w .NET, w tym miejscu operator powinien być typu explicit.

Gufiak - widzę Twoje posty od niedawna i musze stwierdzić, że ... masz imponującą wiedzę o .NET.
Dzięki za komplement. Wiedza może nie imponująca jak piszesz, ale jak na elektronika - programistę samouka źle chyba nie jest ;)
Nie rejestrowałem się, bo miałem zamiar wpaść tutaj tylko na chwilę. Tak się składa, że znalazłem na tym forum wątek poruszający temat, o którym szukałem informacji. Mając kilka chwil przejrzałem tematy i postanowiłem odpisać. W rezultacie z krótkiej chwili zrobiła się nieco dłuższa chwila. Ale ok, zarejestrowałem się. A artykuły, na razie nie bardzo mam jak, bo kilka projektów mam na głowie, ale za jakiś czas... nie wykluczone.

0

int? z = null

Nullable<> jest struktura i jest traktowane specjalnie przez kompilator, i przez CLR pewnie tez. Sprobuj sam:

struct ABC{}

Nullable<int> a = null;
ABC b = null;

to wystarczajaco dowodzi, ze Nullable jest traktowane specjalnie:) zalecam przeczytac teksty bledow*) generowane przez w/w kod, ktore wyraznie pokazuja, ze komplator wykrywa czy typ jest nullable [tekst bledu jest tekstem, nie .ToString'owanym typem!]

gufiak napisał(a)

I tu właśnie jest zagadka, bo zgadzam się z Deti, w kodzie MS .NET faktycznie jest explicit i nie mogę znaleźć przyczyny faktu, że dodawanie działa dla typów nullable, a po użyciu kodu klasy Nullable z Nullable.cs (..)

Nullable<int> a=4, b=5;
int z = a + b;  //1
int? c=3,d=6;
int r = c + d;  //2

kompilacja linijek 1 i 2 wyrzuca ten sam blad*) - zgadnij sam jaki - wiec nie mam zielonego pojecia o czym mowisz..

somekind napisał(a)

Chyba można trzymać obiekt typu T jako pole klasy generycznej i przeciążyć operator dodawania tak, aby działał na wartości tego pola.

nie ma takiej potrzeby. wystarczy ze obiekt sam siebie zapyta o swoj typ poprzez GetType, a potem ow obiekt typu odpyta o GenericParameters i natychmiast sie dowie z jakim <T,U,V> go utworzono

*) - moj kompilator to: msvc 9.0 sp 1, kompilacja w trybie C#2.0

0

quetzalcoatl - jak wytłumaczyć ten fakt:

int? a=1;
int? b=2;
int c = a+b; // błąd: Error	1	Cannot implicitly convert type 'int?' to 'int'. An explicit conversion exists

Mała zmiana:

int? a=1;
int? b=2;
var c = a+b;

Nie ma błędu ! .. a zgadnij co zwraca c.GetType() ;; -> odpowiedź: System.Int32.

0

kompilacja linijek 1 i 2 wyrzuca ten sam blad*) - zgadnij sam jaki - wiec nie mam zielonego pojecia o czym mowisz.
Oczywiście, że wyrzuca błąd. Nullable<T> nie definiuje niejawnego rzutowania do typu wewnętrznego, w tym przypadku do int. O tym była już mowa. Ja mówię o nieco innym przypadku:

Nullable<int> a = 4, b = 5;
int? z = a + b;  //1
int? c = 3, d = 6;
int? r = c + d;  //2

Tu też masz błędy?
To teraz powiedz mi, gdzie jest zdefiniowany operator '+' dla typu Nullable<T>?
Wygląda na to, że pod tym dodawaniem kryją się dwie operacje niejawnego rzutowania do typu wewnętrznego, czyli int (choć teoretycznie nie jest to możliwe), a potem niejawne rzutowanie do Nullable<int>, które jest akurat zdefiniowane.
Jednak przy próbie użycia własnej implementacji typu Nullable<T> (która jest w 100% identyczna z implementacją Nullable<T> Microsoftu, a jedyne co zmieniłem, to nazwę z Nullable<T> na MyNullable<T>), otrzymuję błędy zarówno przy próbie przypisania null, jak i przy operacjach arytmetycznych na tych elementach.

Nullable<> jest struktura i jest traktowane specjalnie przez kompilator, i przez CLR pewnie tez
Nie da się tego wykluczyć, ale dowód, który zaprezentowałeś niekoniecznie mnie przekonuje. "Non-nullable" wcale nie musi oznaczać, że dany typ nie jest typem Nullable<T>, lecz może oznaczać, że jest typem prostym, któremu nie można przypisać null-a. Idąc tym tropem możnaby stwierdzić, że np. klasa jest typem Nullable<T>, bo kompilator wtedy nie wyrzuca tego błędu. Ale pozostałe fakty coraz bardziej przemawiają, że ten typ jest specjalnie traktowany przez kompilator i CLR.
Mało mi się to podoba, bo według mnie Microsoft powinien zaimplementować w języku odpowiednie mechanizmy, które umożliwiałyby implementację tego typu rzeczy w samym kodzie biblioteki, a nie w kodzie kompilatora czy CLR...

// Z tym 100% to przesada, brakuje ci atrybutu TypeDependency :), który jest jako internal. - Deti

0
gufiak napisał(a)

Konwersja do int to półśrodek gdyż, po pierwsze, nie wszystko zmieści się w int-cie, a po drugie, nie wszystko skonwertujesz do int-a

Z int był przykład tylko, można rozpoznawać rzeczywisty typ T i konwertować do niego. Można założyć maksymalną wielkość liczb i używać największego możliwego typu.

chociażby zwykły string się tutaj wyłoży.

Nie wyłoży się, bo go wcale tu nie będzie :) String jest klasą, a T w GenClass mogą być tylko struktury. Ograniczenie jest tak magiczne, że nic co nie jest typem liczbowym do GenClass nie wejdzie.

A jeśli dobrze zrozumiałem massther-a, to nie pyta jak zrobić, żeby '+' działał dla typów numerycznych, a nawet dla typów prostych, a co więcej, nawet nie chodzi o samo Nullable<T>.

Nie wiem, jaki jest jego cel. Zrozumiałem, że chodzi mu o utworzenie typu generycznego zdolnego do przechowywania danych liczbowych i umożliwiającego wykonywanie na nich operacji arytmetycznych.
Toteż napisałem pierwsze rozwiązanie, jakie mi do głowy przyszło, ale jak widać nie dość, że się spóźniłem, to jeszcze było gorsze od Twojego :)

Rozumiem też, że jeśli zrobię swoją własną strukturę, która będzie miała przeciążony operator dodawania, to będę mógł wpakować jej obiekty do MyNullable i potem bez problemu dodawać?

0

var x = int?+int?
int? r = int?+int?

niestety, tekst na forum nie ma szans oddac mojego zdziwienia;)

Wygląda na to, że pod tym dodawaniem kryją się dwie operacje

na pewno. wyglada mi to na bug/hiddenfeature w kompilatorze.: jesli kompilator w tym konkretnym przypadku nie zauwazalby 'explicit' na rzecz implicit, efekt bylby w 100% taki jak otrzymujemy przy kodzie z int? = czy var? = , ale nie tlumaczyloby to prawidlowego zachowania przy int =, chyba ze 'bug' jest az tak specyficzny..

Jednak przy próbie użycia własnej implementacji typu (..) jedyne co zmieniłem, to nazwę z

dlatego wlasnie mowie, ze Nullable<> jest traktowany bardzo specjalnie. W .Net jest wiele specjalnych typow - zerknij na klase System.Enum chociazby, ktorej dzieci (sic!) konstruowane sa w sliczny składniowo sposob, a ktora/ktore faktycznie sa jej dziecmi (choc kompilator tego zabrania komukolwiek) i zachowuja sie bardzo specyficznie (patrz nizej na zrzut z bebechow i na inicjalizacje pol skompilowanego enuma), albo na inne specjalne dziecko Objecta - na klase System.ValueType - ktora defacto jest rodzicem wszystkich struc'tur..

Nullable<> jest struktura i jest traktowane specjalnie przez kompilator, i przez CLR pewnie tez
Nie da się tego wykluczyć, ale dowód, który zaprezentowałeś niekoniecznie mnie przekonuje. "Non-nullable" wcale nie musi oznaczać, (..)

wiem, to byl pierwszy lepszy fakt jaki przyszedl mi do glowy, bez grzebania w bebechach. gdzies w ciemnych kątach pamieci majaczy mi, ze dlubiac reflektorem (acz ciezko porownywac inne poziomy 'niskosci'!) bo klasach ze standardowego .net i po tamtejszych genericach widzialem pare miejsc gdzie nullable byl jawnie traktowany specjalnie. natomiast reflektorem pieknie widac inne paskudne rzeczy, jakie kompilator robi, a jezyk sam w sobie nie pozwala --- np. ow enum i jego dzieci, delegaty, closure'y dla foreach/lambda/yieldreturn, ba, z tego co pamietam dziwy mozna reflektorem takze znalezc w system::string'u gdzie okazuje sie nagle, ze string wcale nie jest niemodyfikowalny!!

kurcze, jeszcze pol roku temu bylbym w stanie wskazac konkretne klasy i metody znajdowalne w oryginalnych assembliach .Net ktore lamia mozliwosci C#'a.. jedyne wytlumaczenie miejscami, to ze albo byly po prostu pisane w czyms innym (np. C++/CLI 0%native), albo ze mieli specjalna wersje kompilatora, albo -- ze kompilator ma jakies fajne undocumented switches..

Mało mi się to podoba, bo według mnie Microsoft powinien zaimplementować w języku odpowiednie mechanizmy, które umożliwiałyby implementację tego typu rzeczy w samym kodzie biblioteki, a nie w kodzie kompilatora czy CLR...

tak, zgadzam sie. to co tu widac jest wiec albo bugiem, albo cholerna niespojnoscia jezyka -- ostatecznie, nie po to definiuja klase nullable i 'alias' int?->nullable<int>, aby zostawiac niespojnosci.. jednak, trudno niezauwazyc faktu, ze struct Nullable jest specjalny podobnie jak Enum, dowod przytoczylem wczesniej, teraz w pelnej formie:

    class X{};

    struct Y{};

    [Serializable]
    public struct MyNullable<T> where T : struct   //zywcem z metadanych + rename. bacz: struct MyNullable
    {
        public MyNullable(T value);

        public static explicit operator T(T? value);
        public static implicit operator T?(T value);

        public bool HasValue { get; }
        public T Value { get; }

        public override bool Equals(object other);
        public override int GetHashCode();
        public T GetValueOrDefault();
        public T GetValueOrDefault(T defaultValue);
        public override string ToString();
    }

    X a = null;     //poprawne
    Y b = null;   //niepoprawane
    Nullable<int> c = null;     //poprawne
    int? d = null;     //poprawne
    MyNullable<int> e = null;   //niepoprawane

to, co naprawde bylo w tym przykladzie z bledem 'non-nullable' najwazniejsze, to fakt, ze kompilator nie pozwala przypisywac null do zmiennych majacych typ struct (czego dowodzi blad dla przypisan zmiennych c oraz e) zas do strucktury Nullable pozwala!

jesli to Ciebie nie przekonuje o specjalnym traktowaniu niektorych typow czy konsturkcji, serio, prosze, wez Reflectora i zobacz co generuje kod:
enum Test{abc, def=2, ghi=-1}
(koniecznie wyswietl to jako C# a potem jako IL, poniewaz jako IL wyglada zupelnie inaczej..)

dla niezReflectorowanych:
'Test' stanie sie klasa '.class public auto ansi serializable sealed RegistryHive extends System.Enum'
'abc' stanie sie '.field public static literal valuetype Test abc = int32(0)'
'def' stanie sie '.field public static literal valuetype Test abc = int32(2)'
'ghi' stanie sie '.field public static literal valuetype Test abc = int32(-1)'

ah.. i jeszcze kompilator dorzuca na koniec:
'.field public specialname rtspecialname int32 value__'
ktorego normalnie w ogole nigdy nie zauwazasz, i ktore (w moim (z)rozumieniu) jest odpowiedzialne za mozliwosc wykonania w ogole przypisania jak powyzej "blahblah.. Test abc = int32(0)" i wewnetrznego przechowywania w obiektach klasy Test wartosci typu int32. tak w ogole, co to ma byc za przypisanie skoro ani Enum ani jego dziecko nie definiuja operatora konwersji/przypisania z int?:)

czyli, defacto, po skompilowanu Test wyglada mniejwiecej tak:

class Test : Enum
{
    public const Test abc = 0; //przyp: const = 'static const' = 'literal' z c++/cli
    public const Test def = 2;
    public const Test ghi = -1;
    
    public int value__; 
}

i dlatego wlasnie w C# jak napiszesz potem "Test-kropka" to Intellisense powie Ci ze w Test sa pola "abc, def, ghi", ot zwykle podpowiedzenie static memberow klasy tyle ze z ikonkami innymi :)
i szczerze, nie sadze zeby w jakimkolwiek przyszlym .Net ten kod sie skompilowal z racji odwiecznej blokady na dziedziczenie po Enum, ktora juz sie pewnie domyslasz czemu jest nałożona..

edit/PS:
ogolnie, rozgadujac sie tak na ten temat, chcialem miedzy wierszami powiedziec, ze CLR / IL to jedno, a C# to drugie.. w IL mozna zdefiniowac bardzo bardzo wiele dziwacznych rzeczy. IL kompilowany wprost z samego siebie obsluguje wszystko, duza wiekszosc jest dostepna z poziomu C++/CLI pod postacia mniej lub bardziej dziwnych __ms_specific_keywords :), po stronie C# sporo juz jest zablokowane, jak np - kombinacje dozwolonych specyfikatorow widocznosci/wirtualnosci dla gettera/settera, mimo ze CLR/IL obsluguje praktycznie wszystkie mozliwe kombinacje poprawnie..

0

Nie ma błędu ! .. a zgadnij co zwraca c.GetType() ;; -> odpowiedź: System.Int32.
A sprawdź co zwraca b.GetType() ;) IntelliSense podpowie Ci jaki faktycznie typ kryje się pod c.

// Z tym 100% to przesada, brakuje ci atrybutu TypeDependency , który jest jako internal. - Deti
Jest dostępny w źródłach. Ale racja, ze 100% przesadziłem, bo musiałem zmienić linię z "ThrowHelper" ;)

Ale posunąłem się dalej. Nie zmieniłem tym razem nazwy, bo w końcu inny namespace, więc gryźć się nie będzie. Niestety, nazwa ta sama co w oryginalnym typie, a błędy są nadal. Więc dodatkowo umieściłem w namespace "System" i co ciekawe pokazało ostrzeżenia, że jeden typ Nullable gryzie się z drugim, ale otrzymałem również dwa błędy, które dotyczyły tradycyjnie przypisania null-a oraz dodawania na moim Nullable<T>. W efekcie otrzymałem error, którego normalnie nie zobaczycie:

Cannot convert null to 'System.Nullable<int>' because it is a non-nullable value type
oraz
Operator '+' cannot be applied to operands of type 'System.Nullable<int>' and 'System.Nullable<int>'
:)

Z int był przykład tylko, można rozpoznawać rzeczywisty typ T i konwertować do niego. Można założyć maksymalną wielkość liczb i używać największego możliwego typu.
Ale to nadal nie jest najlepsze rozwiązanie. Ze stringiem to faktycznie wyskoczyłem bez przemyślenia. Tyle, że w C# również struktura jest typem prostym. I wtedy klops, bez znajomości struktury nie zadziała ta metoda. A pierwsza lepsza taka struktura to np. liczba zespolona. Mogą być na niej wykonywane operacje, a do przechowywania potrzebna jest struktura. Ale niemniej jednak, są sposoby, bo wystarczy albo męczyć się z jawnym rzutowaniem, albo użyć implicit i rzutowanie niejawne.
Dziwne dla mnie jest tylko, że Nullable raz stosuje konwersję jawną, a innym razem niejawną. Niekonsekwencja, ale na szczęście ta niekonsekwencja istnieje tylko dla naszej wygody. Dzięki niej możemy operować jak na oryginalnym typie, a i kompilator zwróci nam uwagę jak niechcący pomieszamy np. int? z int.

quetzalcoatl, Enum czy ValueType to tak na prawdę sztuczne twory, próba z czegoś naturalnie nieobiektowego zrobić coś obiektowego. Ich specjalne traktowanie przez kompilator/CLR wcale mnie nie dziwi. Trudno by było zrobić enuma jako klasę bez sztuczek. Tak samo ValueType sugeruje nam, że liczba jest obiektem... Ale wszystko dla wygody programisty. Dziwi mnie tylko specjalne traktowanie typu Nullable, bo mechanizmy w nim użyte powinny być zaimplementowane w języku, nie w kompilatorze/CLR.

0
gufiak napisał(a)

Dziwne dla mnie jest tylko, że Nullable raz stosuje konwersję jawną, a innym razem niejawną. Niekonsekwencja, ale na szczęście ta niekonsekwencja istnieje tylko dla naszej wygody. Dzięki niej możemy operować jak na oryginalnym typie, a i kompilator zwróci nam uwagę jak niechcący pomieszamy np. int? z int.

http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx :

Boxing and Unboxing
When a nullable type is boxed, the common language runtime automatically boxes the underlying value of the Nullable<(Of <(T>)>) object, not the Nullable<(Of <(T>)>) object itself. That is, if the HasValue property is true, the contents of the Value property is boxed. When the underlying value of a nullable type is unboxed, the common language runtime creates a new Nullable<(Of <(T>)>) structure initialized to the underlying value(...)

podobno wiec nawet CLR traktuje go specjalnie:)

Btw. jak juz jestesmy przy nullablach..:

    public struct X
    {
        public int value;
        public static X operator +(X lhs, X rhs) { return new X { value = lhs.value + rhs.value }; }
    }

    X a = new X{ value=1}, b = new X{value = 2};
    X? A = a, B = b, C = null, D = null;

    X r0 = a + b; // ok, 3
    X r1 = a + A; // cannot implicitly convert X? -> X
    X? r2 = A + a; // ok, 2
    X? r3 = A + B; // ok, 3
    X? r4 = B + C; // ok, null
    X? r5 = C + D; // ok, null

http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx

The predefined unary and binary operators and any user-defined operators that exist for value types may also be used by nullable types. These operators produce a null value if the operands are null(..)

co tlumaczy w/w zachowanie z int?+int? i blad konwesji na int -- operator+ zwraca int?, blad tyczyl sie konwersji retval nie params! nie zachodzi tutaj konwersja implicit na X (gdyz wtedy polecialby InvalidOperationException jako ze jeden z parametrow operatora+ jest null), tylko dla typow Nullable przewidziano 'forwardowanie' operatorow typow bazowych, z dodatkowym ficzerem, ze oryginalny operatow nie jest wywolywany jesli jeden z parametrow !HasValue, jest to w owym artykule na msdn wyraznie na pisane, i za pewne dlatego wlasnie kod maszynowy wynikajacy z int?+int? wyglada tak a nie inaczej

0
quetzalcoatl napisał(a)

//przyp: const = 'static readonly'

Że niby to to samo?

gufiak napisał(a)

Ale to nadal nie jest najlepsze rozwiązanie.

To już ustaliliśmy. A możesz odpowiedzieć na moje pytanie z poprzedniego posta?

0
somekind napisał(a)
quetzalcoatl napisał(a)

//przyp: const = 'static readonly'

Że niby to to samo?

nie, nie jest, tam mialo byc 'static const', mieszalem C# z C++/CLI i mi sie terminy majtnęły, poprawione

0

podobno wiec nawet CLR traktuje go specjalnie:)
To właśnie jeden z tych faktów, które przekonały mnie o specjalnym traktowaniu typu Nullable.
Ale pod Reflektorem wszystko staje się jasne. Kompilator sam generuje odpowiedni kod dla Nullable<T> i jeśli któryś z parametrów jest pusty, to wywołuje domyślny konstruktor struktury. Dodawanie odbywa się w ten sposób, że sprawdza obydwa parametry i jeśli oba nie są puste, to wykonuje dodawanie na wewnętrznym typie pobierając wartości używając metody GetValueOrDefault(). Jest to w pewnym sensie rzutowanie niejawne (nie określone w definicji typu Nullable<T>), ale kontrolowane w pełni przez kompilator, który pozwala rzutować niejawnie jedynie gdy Nullable<T> jest użyty przy operatorze.

To już ustaliliśmy. A możesz odpowiedzieć na moje pytanie z poprzedniego posta?
Jeśli dobrze widzę, że jest tylko jedno pytanie, to chyba odpowiedź jest zbędna? Przecież o to w całym tym wątku chodzi. To jest właśnie sedno sprawy i o to pytał massther w swoim poście. Microsoft zrobił sobie specjalną obsługę w kompilatorze dla takiego przypadku, a nam pozostaje implicit i ryzyko, że gdzieś pomieszamy typy, lub explicit i męczenie się z rzutowaniem, albo inne kombinacje.

0

bylem na urlopie (w takim kraju gdzie ciezko o net) wiec nie sledzilem watku
fajna dyskusja sie wywiazala

po przeczytaniu jednak nie udalo mi sie jakos sensownie ruszyc z miejsca, wiec moze napisze co konkretnie chcialem osiagnac
chcialem zaimplementowac strukture Infinitable<T>
ktora moglaby przechowywac wartosc typu T lub -/+ nieskonczonosc
implementacja podobna do nullable<T>
problem pojawia sie przy dodawaniu (lub innych operacjach) struktur infinitable

public static Infinitable<T> operator +(Infinitable<T> x, Infinitable<T> y)
{
if (x.IsInfinite || y.IsInfinite)
return Infinitable<T>.PositiveInfinite; // oczywiscie odpowiednie przyp. dla -/+ niesk.
else
return (T)x + (T)y; // ???
}
oczywiscie operator + z typu T moge za pomoca reflection uzyc (wiem jak to zrobic, wiec mozecie nie opisywac :)), ale czy jest inny sposob?

wlasny GetType (zwracajacy typeof(T)) i nie implementowanie operator + dziala tylko jesli dodaje infinitable ktore maja wartosci

public struct Infinitable<T>
    {
        public enum InfiniteType { Negative, Positive }

        public static Infinitable<T> PositiveInfinite
        {
            get { return new Infinitable<T>(Infinitable<T>.InfiniteType.Positive); }
        }
        public static Infinitable<T> NegativeInfinite
        {
            get { return new Infinitable<T>(Infinitable<T>.InfiniteType.Negative); }
        }

        #region constructors
        public Infinitable(T value)
        {
            _infinite = null;
            _value = value;
        }
        public Infinitable(InfiniteType inf)
        {
            _value = default(T);
            _infinite = inf;
        }
        #endregion

        #region fields
        private T _value;
        private InfiniteType? _infinite;
        #endregion

        #region properties
        public T Value 
        { 
            get 
            {
                if (_infinite.HasValue)
                    throw new InvalidOperationException();
                return _value; 
            }
        }
        public bool IsInfinite { get { return _infinite.HasValue; } }
        public bool IsPositiveInfinite { get { return _infinite == Infinitable<T>.InfiniteType.Positive; } }
        public bool IsNegativeInfinite { get { return _infinite == Infinitable<T>.InfiniteType.Negative; } }
        #endregion

        #region operators
        public static implicit operator Infinitable<T>(T value)
        {
            return new Infinitable<T>(value);
        }
        public static implicit operator T(Infinitable<T> value)
        {
            return value.Value;
        }
        #endregion

        public override string ToString()
        {
            return IsInfinite ? _infinite + " Inifinite" : _value.ToString();
        }

        public new Type GetType()
        {
            return typeof(T);
        }
        static object Box(Infinitable<T> o)
        {
            return o;
        }

        static Infinitable<T> Unbox(object o)
        {
            return (Infinitable<T>)o;
        }
    }
0

pech w tym, ze generics to nie template z C++.. zebys mogl wykonac (T)x + (T)y, musialbys dodac constraint'a mowiacego ze typ T ma sie cechowac operatorem+() -- a na tyle na ile pamietam, na razie jedyne constrainty jakie mozna deklarowac to interfejs,typ,class/struct oraz new()

wydaje mi sie, ze w tym przypadku pozostaje juz tylko reflection lub dorzucenie do Twojego typu danych implementacji interejsu i masa paskudztw:

public struct Infinitable<T, U>
    where T : IBasicArithmeticOps<Infinitable<T, U>, Infinitable<T, U>>, IValue< U>, new()
{
    public enum InfiniteType { Negative, Positive }

    public static Infinitable<T, U> PositiveInfinite { get { return new Infinitable<T, U>(Infinitable<T, U>.InfiniteType.Positive); } }

public static Infinitable<T, U> NegativeInfinite { get { return new Infinitable<T, U>(Infinitable<T, U>.InfiniteType.Negative); } }

    #region constructors
    public Infinitable(T value) { _infinite = null; _value = value; }
    public Infinitable(InfiniteType inf) { _value = default(T); _infinite = inf; }
    #endregion

    #region fields
    private T _value;
    private InfiniteType? _infinite;
    #endregion

    #region properties
    public T Value { get { if (_infinite.HasValue) throw new InvalidOperationException(); return _value; } }
    public bool IsInfinite { get { return _infinite.HasValue; } }
    public bool IsPositiveInfinite { get { return _infinite == Infinitable<T, U>.InfiniteType.Positive; } }
    public bool IsNegativeInfinite { get { return _infinite == Infinitable<T, U>.InfiniteType.Negative; } }
    #endregion

    public override string ToString() { return IsInfinite ? _infinite + " Inifinite" : _value.Value.ToString(); }

    public new Type GetType() { return typeof(T); }
    static object Box(Infinitable<T, U> o) { return o; }
    static Infinitable<T, U> Unbox(object o) { return (Infinitable<T, U>)o; }

    #region operators

    public static implicit operator Infinitable<T, U>(U value) { var tmp = new T(); tmp.Value = value; return new Infinitable<T,U>(tmp); }
    public static implicit operator U(Infinitable<T, U> value) { return value.Value.Value; }

    public static Infinitable<T, U> operator +(Infinitable<T, U> lhs, Infinitable<T, U> rhs)
    {
        if (lhs.IsInfinite || rhs.IsInfinite)
        {
            var any = lhs._infinite ?? rhs._infinite;
            var yna = rhs._infinite ?? lhs._infinite;
            if (any != yna) throw new ArgumentException("Result of the operation is undefined");
            return new Infinitable<T, U>(any.Value);
        }
        return lhs.Value.Add(rhs);
    }
    
    // itd inne ops

    #endregion
}
public interface IValue<T>
{
    T Value { get; set; }
}

public interface IBasicArithmeticOps<TRight, TResult>
{
    TResult Add(TRight rhs);

    // itd inne ops
}
public struct IdioticIntBoxture :
    IBasicArithmeticOps<Infinitable<IdioticIntBoxture, int>, Infinitable<IdioticIntBoxture, int>>,
    IValue<int>
{
    public static implicit operator int(IdioticIntBoxture value) { return value.Value; }
    public static implicit operator IdioticIntBoxture(int value) { return new IdioticIntBoxture() { Value = value }; }

    public int Value { get; set; }

    public Infinitable<IdioticIntBoxture, int> Add(Infinitable<IdioticIntBoxture, int> rhs)
    {
        int l = Value, r = rhs, res = l + r;

        if (l >= 0 != r >= 0)
            return res; // 100% safe operation

        if (l > 0)
            if (res < l)
                return Infinitable<IdioticIntBoxture, int>.PositiveInfinite;
            else
                return res;

        if (res > l)
            return Infinitable<IdioticIntBoxture, int>.NegativeInfinite;

        return res;
    }

    // itd inne ops
}
class Test
{
    static void Main(string[] args)
    {
        Infinitable<IdioticIntBoxture, int> a, b, c, d, e, f;
        a = 0x7ffffffe; 
        b = 1;
        c = -1;
        d = -0x7fffffff-1;
        e = new Infinitable<IdioticIntBoxture, int>(Infinitable<IdioticIntBoxture, int>.InfiniteType.Positive);
        f = new Infinitable<IdioticIntBoxture, int>(Infinitable<IdioticIntBoxture, int>.InfiniteType.Negative);
        //BTW: skoro static InfiniteType.Positive, to warto dodac tez juz-opakowane static Infinitable.PositiveInfinity
        //bedzie krotsze uzycie

        //dziala? dziala. ale jest paskudne :| zeby jeszcze w C# byly typedef, eh.
        //zeby to jeszcze byly klasy, to by sie dalo nazwe typu skrocic, EH..

        Infinitable<IdioticIntBoxture, int> r0 = a + b; // 2147483647
        Infinitable<IdioticIntBoxture, int> r1 = a + 2; // +inf
        Infinitable<IdioticIntBoxture, int> r2 = b + c; // 0
        Infinitable<IdioticIntBoxture, int> r3 = c + d; // -inf
        Infinitable<IdioticIntBoxture, int> r4 = d + e; // +inf
        Infinitable<IdioticIntBoxture, int> r5 = e + f; // except: (+inf) + (-inf) = undefined, przeciez nie zero
    }
}

dziala i ma ten maly plus, ze nie uzywa reflection..

a za to wprowadza oczywiste minusy:

  • infinitable jest ubrzydzone ekstra parametrem, ktory na dobra sprawe jest dla uzytkownika Infinitable bezsensu
  • struktura IdioticIntBoxture co prawda opakowuje dodatkowo int'a, ale -CHYBA- nie wprowadza wiekszego narzutu na pamiec- interfejs IValue pechowo jest wymagany, gdyz... nie mozna zdefiniowac constrainta oznaczajacego parametryczny konstruktor

i jeden nieoczywisty:

  • wydaje mi sie, ze grozi type-bloatem w momencie gdy trzeba bedzie dostarczyc wielu roznych krzyzowych konwersji

//----------------------------
edit:
tak po dodatkowym przemysleniu -- na dobra sprawe, nazwa IdioticIntBoxture jest nie na miejscu. cos w stylu tej klasa musigdzies istniec: przeciez Infinitable dostarcza obslugi infinity+val, val+infinity, infinity+infinity, a cos musi rowniez dostarczac wiedzy na temat: kiedy operacja val+val ma wynik +/-infinity !! i na dobra sprawe, to wlasnie jest jej glownym zadaniem - nawet przypadkiem mi to w kodzie wyszlo.

Ciekawe jak to po skompilowaniu wyglada. Zapewne metody nie beda zinlineowane, zbyt duzo sciezek..
Tak czy owak, sadze ze poza byciem wątpliwym 'proof-of-a-concept', jest to rozwiazanie malo przydatne z racji dwuparametrowosci Infinitable. Jeszcze jakby to mogla byc CLASS -- wtedy mozna by bylo pokusic sie o ladniej wygladajace niby-specjalizacje, np:

class InfiInt : Infinitiable<intimplem, int>
{
     public static operator..
     public static operator..
     public static operator..
}

ale to sa struct, wiec nie mozna:)

1 użytkowników online, w tym zalogowanych: 0, gości: 1