W .NET typ wartości (C # struct
) nie może mieć konstruktora bez parametrów. Zgodnie z tym postem jest to wymagane przez specyfikację CLI. Co się dzieje, że dla każdego typu wartości tworzony jest domyślny konstruktor (przez kompilator?), Który inicjuje wszystkie elementy na zero (lub null
).
Dlaczego nie wolno definiować takiego domyślnego konstruktora?
Jednym trywialnym zastosowaniem są liczby wymierne:
public struct Rational {
private long numerator;
private long denominator;
public Rational(long num, long denom)
{ /* Todo: Find GCD etc. */ }
public Rational(long num)
{
numerator = num;
denominator = 1;
}
public Rational() // This is not allowed
{
numerator = 0;
denominator = 1;
}
}
Używając bieżącej wersji C #, domyślnym produktem Rational jest to, 0/0
co nie jest tak fajne.
PS : Czy domyślne parametry pomogą rozwiązać ten problem w C # 4.0, czy też zostanie wywołany domyślny konstruktor zdefiniowany w CLR?
Jon Skeet odpowiedział:
Na przykład, co chciałbyś zrobić, gdy ktoś to zrobił:
Rational[] fractions = new Rational[1000];
Czy powinien przejść przez twój konstruktor 1000 razy?
Pewnie, że właśnie dlatego napisałem domyślnego konstruktora. CLR powinien używać domyślnego konstruktora zerowania, gdy nie zdefiniowano żadnego jawnego domyślnego konstruktora; w ten sposób płacisz tylko za to, czego używasz. Następnie, jeśli chcę kontener 1000 domyślnych Rational
(i chcę zoptymalizować 1000 konstrukcji), użyję List<Rational>
raczej tablicy niż tablicy.
Moim zdaniem ten powód nie jest wystarczająco silny, aby zapobiec definicji domyślnego konstruktora.
Rational()
wywołuje raczej parametryczny ctor niżRational(long num=0, long denom=1)
.new Rational()
wywoła konstruktor, jeśli istnieje, jednak jeśli nie istnieje,new Rational()
będzie równoważnydefault(Rational)
. W każdym razie zachęca się do użycia składni,default(Rational)
gdy chce się „zerowej wartości” struktury (która jest „złą” liczbą w proponowanym projekcieRational
). Domyślną wartością dla typu wartościT
jest zawszedefault(T)
. Dlategonew Rational[1000]
nigdy nie będzie wywoływał konstruktorów struktur.denominator - 1
w strukturze, aby domyślna wartośćThen if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational> rather than an array.
Dlaczego miałbyś oczekiwać, że tablica wywoła inny konstruktor do listy dla struktury?Odpowiedzi:
Uwaga: poniższa odpowiedź została napisana na długo przed C # 6, który planuje wprowadzić możliwość deklarowania konstruktorów bez parametrów w strukturach - ale nadal nie będą wywoływane we wszystkich sytuacjach (np. Do tworzenia tablicy)(na końcu ta funkcja nie została dodana do C # 6 ).EDYCJA: Zedytowałem odpowiedź poniżej ze względu na wgląd Grauenwolfa w CLR.
CLR pozwala typom wartości mieć konstruktory bez parametrów, ale C # nie. Wierzę, że dzieje się tak, ponieważ wprowadziłoby oczekiwanie, że konstruktor zostanie wywołany, gdy nie będzie. Rozważmy na przykład:
CLR jest w stanie to zrobić bardzo skutecznie, po prostu przydzielając odpowiednią pamięć i zerując wszystko. Gdyby musiał uruchomić konstruktor MyStruct 1000 razy, byłoby to znacznie mniej wydajne. (W rzeczywistości tak nie jest - jeśli zrobić mieć konstruktora bez parametrów, to nie ma się uruchomić po utworzeniu tablicy, albo gdy masz niezainicjowanej zmiennej instancji).
Podstawową zasadą w języku C # jest „domyślna wartość dla dowolnego typu nie może polegać na żadnej inicjalizacji”. Teraz mogliby zezwolić na zdefiniowanie konstruktorów bez parametrów, ale wtedy nie wymagali wykonywania tego konstruktora we wszystkich przypadkach - ale doprowadziłoby to do większego zamieszania. (A przynajmniej tak mi się wydaje, że argument jest taki.)
EDYCJA: Aby użyć Twojego przykładu, co chciałbyś zrobić, gdy ktoś to zrobił:
Czy powinien przejść przez twój konstruktor 1000 razy?
EDYCJA: (Odpowiadając nieco na pytanie) Konstruktor nie tworzy parametrów. Typy wartości nie muszą mieć konstruktorów, jeśli chodzi o CLR - chociaż okazuje się, że może, jeśli napiszesz je w IL. Gdy piszesz
new Guid()
w języku C #, który emituje inną IL do tego, co otrzymujesz, jeśli wywołasz normalnego konstruktora. Zobacz to SO pytanie, aby uzyskać więcej informacji na ten temat.I podejrzewam, że nie ma żadnych typy wartości w ramach z konstruktorów bez parametrów. Bez wątpienia NDepend mógłby mi powiedzieć, gdybym zadał to wystarczająco ładnie ... Fakt, że C # zabrania tego jest wystarczająco dużą wskazówką, aby pomyśleć, że to prawdopodobnie zły pomysł.
źródło
Rational[] fractions = new Rational[1000];
marnuje również obciążenie pracą, jeśliRational
jest strukturą zamiast struktury? Jeśli tak, to dlaczego klasy mają domyślny ctor?Rational
jest to klasa, otrzymasz tablicę 1000 referencji zerowych.Struktur jest typem wartości, a typ wartości musi mieć wartość domyślną, gdy tylko zostanie zadeklarowany.
Jeśli zadeklarujesz dwa pola jak powyżej bez tworzenia instancji, zepsuj debugger,
m
będzie miał wartość zerową, alem2
nie będzie. Biorąc to pod uwagę, bezparametrowy konstruktor nie miałby sensu, w rzeczywistości każdy konstruktor w strukturach przypisuje wartości, sama rzecz istnieje już po prostu poprzez jej zadeklarowanie. Rzeczywiście m2 można z powodzeniem wykorzystać w powyższym przykładzie i wywołać jej metody, jeśli w ogóle, a pola i właściwości zmanipulować!źródło
new
It, a następnie próbie użycia jej członkówChociaż CLR na to pozwala, C # nie pozwala strukturom na domyślny konstruktor bez parametrów. Powodem jest to, że dla typu wartości kompilatory domyślnie nie generują domyślnego konstruktora ani nie generują wywołania do domyślnego konstruktora. Tak więc, nawet jeśli zdarzyło Ci się zdefiniować domyślny konstruktor, nie zostanie on wywołany, a to tylko dezorientuje.
Aby uniknąć takich problemów, kompilator C # nie zezwala użytkownikowi na zdefiniowanie domyślnego konstruktora. A ponieważ nie generuje domyślnego konstruktora, nie można inicjować pól podczas ich definiowania.
Lub głównym powodem jest to, że struktura jest typem wartości, a typy wartości są inicjowane przez wartość domyślną, a do inicjalizacji używany jest konstruktor.
Nie musisz tworzyć instancji swojej struktury za pomocą
new
słowa kluczowego. Zamiast tego działa jak int; możesz uzyskać do niego bezpośredni dostęp.Struktury nie mogą zawierać jawnych konstruktorów bez parametrów. Elementy konstrukcyjne są automatycznie inicjowane do wartości domyślnych.
Domyślny konstruktor (bez parametrów) dla struktury może ustawić inne wartości niż stan zerowania, co byłoby nieoczekiwanym zachowaniem. Środowisko wykonawcze .NET zabrania zatem domyślnych konstruktorów dla struktury.
źródło
MyStruct s;
nie wywoływanie domyślnego konstruktora, który podałeś.Możesz utworzyć właściwość statyczną, która inicjuje i zwraca domyślną liczbę „wymierną”:
I używaj go w następujący sposób:
źródło
Rational.Zero
może być nieco mniej mylące.Krótsze wyjaśnienie:
W C ++ struktury i klasa były tylko dwiema stronami tej samej monety. Jedyną prawdziwą różnicą jest to, że jedna była domyślnie publiczna, a druga prywatna.
W .NET istnieje znacznie większa różnica między strukturą a klasą. Najważniejsze jest to, że struct zapewnia semantykę typu wartości, podczas gdy klasa zapewnia semantykę typu odniesienia. Kiedy zaczynasz myśleć o konsekwencjach tej zmiany, inne zmiany również stają się bardziej sensowne, w tym opisywane zachowanie konstruktora.
źródło
new
naprawdę musi być napisane, aby wywołać konstruktora. W C ++ konstruktory są wywoływane w ukryty sposób, przy deklaracji lub instancji tablic. W języku C # albo wszystko jest wskaźnikiem, więc zacznij od zera, albo jest strukturą i musi zaczynać od czegoś, ale gdy nie możesz napisaćnew
... (np. Init tablicy), złamałoby to silną regułę C #.Nie widziałem odpowiednika spóźnionego rozwiązania, które dam, więc oto jest.
użyj przesunięć, aby przenieść wartości z domyślnego 0 na dowolną wartość, którą lubisz. tutaj należy użyć właściwości zamiast bezpośredniego dostępu do pól. (być może z możliwą funkcją c # 7 lepiej zdefiniujesz pola o zakresie właściwości, aby były chronione przed bezpośrednim dostępem do kodu).
To rozwiązanie działa dla prostych struktur z tylko typami wartości (bez typu ref lub struktury zerowalnej).
To różni się od tej odpowiedzi, to podejście nie jest specjalną obudową, ale wykorzystuje przesunięcie, które będzie działać dla wszystkich zakresów.
przykład z wyliczeniami jako polem.
Jak powiedziałem, ta sztuczka może nie działać we wszystkich przypadkach, nawet jeśli struct ma tylko pola wartości, tylko ty wiesz, że jeśli działa w twoim przypadku, czy nie. po prostu zbadaj. ale masz ogólny pomysł.
źródło
Tylko w specjalnym przypadku. Jeśli zobaczysz licznik 0 i mianownik 0, udawaj, że ma wartości, których naprawdę chcesz.
źródło
Nullable<T>
(npint?
.).new Rational(x,y)
gdzie xiy ma wartość 0?To, czego używam, to operator koalescencji zerowej (??) w połączeniu z polem podkładowym, takim jak to:
Mam nadzieję że to pomoże ;)
Uwaga: zerowe przypisanie koalescencyjne jest obecnie propozycją funkcji dla C # 8.0.
źródło
Nie możesz zdefiniować domyślnego konstruktora, ponieważ używasz C #.
Struktury mogą mieć domyślne konstruktory w .NET, chociaż nie znam żadnego konkretnego języka, który by je wspierał.
źródło
Oto moje rozwiązanie dylematu braku domyślnego konstruktora. Wiem, że to późne rozwiązanie, ale myślę, że warto zauważyć, że to rozwiązanie.
ignorując fakt, że mam strukturę statyczną o nazwie null, (uwaga: dotyczy tylko wszystkich dodatnich ćwiartek), używając get; set; w języku C # możesz spróbować try / catch / wreszcie, aby poradzić sobie z błędami, w których określony typ danych nie jest inicjowany przez domyślny konstruktor Point2D (). Wydaje mi się, że jest to nieuchwytne rozwiązanie dla niektórych osób z tą odpowiedzią. Właśnie dlatego dodaję moje. Użycie funkcji pobierających i ustawiających w języku C # pozwoli ci ominąć ten domyślny konstrukt bez sensu i spróbować złapać to, czego nie zainicjowałeś. Dla mnie to działa dobrze, dla kogoś innego możesz chcieć dodać kilka instrukcji if. Tak więc w przypadku, gdy chcesz skonfigurować licznik / mianownik, ten kod może pomóc. Chciałbym tylko powtórzyć, że to rozwiązanie nie wygląda dobrze, prawdopodobnie działa jeszcze gorzej z punktu widzenia wydajności, ale, dla kogoś, kto pochodzi ze starszej wersji C #, użycie tablicowych typów danych daje tę funkcjonalność. Jeśli chcesz tylko czegoś, co działa, spróbuj tego:
źródło
źródło