Klasy abstrakcyjne java a C#

0

Zainspirował mnie ten temat: Klasy abstrakcyjne
i postanowiłem napisać coś takiego tylko, że w C#:

/*
Utwórz klasę bazową z abstrakcyjną metodą print(), przesłoniętą w klasach pochodnych.

  • Wersja przesłonięta metody ma wypisywać wartość składowej int zdefiniowanej w klasie pochodnej.
  • W miejscu definiowania zmiennej nadaj jej wartość niezerową. W konstruktorze klasy bazowej wywołaj metodę.
  • W metodzie main() utwórz obiekt klasy pochodnej, a potem wywołaj na jego rzecz metodę print(). Wyjaśnij zaobserwowane efekty.

*/

 using System;

abstract class bazowa
{
    public abstract void Print();
    public bazowa()
    {Print();}

}
class pochodna : bazowa
{
    private int zmienna = 4;
    public override void Print()
    {
        Console.WriteLine(zmienna);
    }
}

class Abstrakcja
{

    static void Main()
    {
        pochodna p = new pochodna();
        p.Print();

        
        Console.ReadLine();
    }
}

Wyniki, które ja dostaje to 4 i 4.... dlaczego?:)
Tamten gostek dostawał 0 i 10 bo jego zmienna wynosiła 10 a moje 4... ocb?

3

Bo C# jest sprytniejszy (pod tym względem) niż Java :>

W C#, podczas wywoływania metody wirtualnej masz pewność że klasa ją zawierająca jest w pełni zainicjalizowana. W Javie, jak widać, nie masz. A w C++ to dopiero cuda się dzieją :].

Tak czy inaczej, takie zachowanie jest chyba intuicyjne, prawda? Print() wypisuje zmienna, zmienna jest zawsze 4, dlaczego to ma działać inaczej?

0

@msm: w sumie nie cuda, w C++ przy próbie wywołania metody abstrakcyjnej w klasie bazowej dostaniesz unresolved external i tyle.

2

@byku_guzio - jesteś pewien?

Cytując klasyka - http://www.horstmann.com/cpp/pitfalls.html :

class Shape
{  
public:
    Shape();

private:
    void init();
    virtual void reset() = 0;
}; 

Shape::Shape() { init(); }
void Shape::init() { reset(); } 

class Point : public Shape
{  
public:
    virtual void reset();

private:
    double _x, _y;
};

void Point::reset() { _x = _y = 0; }

int main()
{
    Point p;
    return 0;
} 

Polecam przetestować.

0

Nie sprytniejsze tylko ma inną kolejność inicjalizacji.

W C#, podczas wywoływania metody wirtualnej masz pewność że klasa ją zawierająca jest w pełni zainicjalizowana.

Jeśli w konstruktorze mam wywołanie metody wirtualnej nadpisanej w klasie podrzędnej i ta metoda w klasie podrzędnej korzysta z elementów inicjowanych w konstruktorze klasy podrzędnej to co się wtedy stanie w tym sprytnym języku?

1

Rzeczywiście. Dlatego C++ to jeden z najciekawszych języków ;)

4

http://ideone.com/HMIVs

Różnica między C# a Javą jest więc taka:

Java leci najpierw od najbardziej ogólnej klasy do najbardziej szczegółowej i na każdym etapie wykonuje najpierw bloki inicjalizacyjne (w tym inicjalizacja zmiennyc inline) a potem konstruktor.

C# robi to w dwóch przejściach, tzn najpierw leci po hierarchii i wykonuje bloki inicjalizacyjne, a potem leci po hierarchii drugi raz ale tym razem wykonuje konstruktory.

Zalezy co komu pasuje, wg mnie bardziej logiczne jest podejście Javowe.

0

W jaki jeszcze sposób można w C# dokonać inicializacji pól prócz tego sposobu z przykładu pierwszego?

private int zmienna = 4;
 
0
 pochodna p;
p = new pochodna();

O to Ci chodzi?

0

private int zmienna;
konstruktor() {
zmienna = 4;
}

0

To co napisałeś nie jest inicializajca. W tym wypadku zmienna będzie zainicializowana wartością 0, a następnie zostanie przypisane 4. Chodzi mi o coś na wzór listy inicializujacej w C++.

Chodzi mi o to aby 2x nie przypisywać wartości (w twoim wypadku 0 i 4).

0

Czemu się taki kod w C# nie kompiluje:

using System;

abstract class bazowa
{
    public int zmienna1 = 5; 
}
class pochodna : bazowa
{
    public int zmienna = zmienna1 + 4;
}

public class Test
{
	public static void Main()
	{
	}
}

?

Tzn jaka jest motywacja tego, że odwoływanie się do pól z klas nadrzędnych przy inicjalizacji pól jest zabronione?

Przy inicjalizacji konstruktorem już się kompiluje:

abstract class bazowa
{
    public int zmienna1 = 5; 
}
class pochodna : bazowa
{
    public int zmienna;
    pochodna() {
        zmienna = zmienna1 + 5;
    }
}

Chodzi mi o to aby 2x nie przypisywać wartości (w twoim wypadku 0 i 4).

Żaden argument, przyzwoity kompilator może to sobie zoptymalizować do jednej inicjalizacji, poza tym jeśli inicjuję konstruktorem to i tak chyba zostają dwa przypisania, no nie?

tym to mnie już c# rozwalił:
http://ideone.com/n4byM

class c
{
    public int a = 8;
    public int b = a + 5;
}

prog.cs(6,20): error CS0236: A field initializer cannot reference the nonstatic field, method, or property c.a' prog.cs(13,37): error CS0120: An object reference is required to access non-static member c.b'
Compilation failed: 2 error(s), 0 warnings

0

Żaden argument, przyzwoity kompilator może to sobie zoptymalizować do jednej inicjalizacji, poza tym jeśli inicjuję konstruktorem to i tak chyba zostają dwa przypisania, no nie?

Mi sie wydaje ze ciezko by mu było.

  class Klasa
        {
            public int zmienna; //w takiej sytuacji zawsze jest tu konstruktor domyślny wywołany
            public int nowaZmienna = 5; //tutaj nie wiem co się dzieje, czy konstruktor domyślny + przypisanie, albo konstruktor parametryczny

            public Klasa()
            {
                Console.WriteLine(zmienna);
                zmienna = 5; // czarno na białym widać ze jak będziemy przypisywać w konstruktorze, to zmienna będzie musiała posiadać dwie wartości 0 a //potem 5
                Console.WriteLine(zmienna);
            }
        } 
0

Myślę, że optymalizacja czegoś takiego to pikuś dla JVMa. Zobacz tu: http://4programmers.net/Forum/C_i_.NET/175596-dlaczego_foreach_jest_gorsze_od_for_beta?p=721867#id721867

0 może sobie wrzucić do WriteLine bezpośrednio.

0

Nie wiem dlaczego skasowałeś mój i swój komentarz ze swojego postu. Ale wspominałeś tam ze finala możesz zainicializowac w konstruktorze. I to może być przyczyna tej optymalizacji.

2
using System;
 
abstract class bazowa
{
    public int zmienna1 = 5; 
}
class pochodna : bazowa
{
    public int zmienna = zmienna1 + 4;
}
 
public class Test
{
        public static void Main()
        {
        }
}

Bo C# to nie Java, satysfakcjonuje Cię taka odpowiedź? :>

wibowit napisał(a)

tym to mnie już c# rozwalił:

class c
{
    public int a = 8;
    public int b = a + 5;
}

Patrz wyżej...

17.4.5.2 Instance field initialization

The instance field variable initializers of a class correspond to a sequence of assignments that are executed
immediately upon entry to any one of the instance constructors (§ 17.10.2) of that class. The variable
initializers are executed in the textual order in which they appear in the class declaration. The class instance
creation and initialization process is described further in §17.10.

A variable initializer for an instance field cannot reference the instance being created. Thus, it is a compile-
time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to
reference any instance member through a simple-name
. [Example : In the following code

     class A 
     { 
          int x = 1; 
          int y = x + 1;          // Error, reference to instance member of this 
     } 

the variable initializer for y results in a compile-time error because it references a member of the instance
being created. end example]

Dlaczego? IMO, żeby uniknąć zamieszania z circular referencjami (przy statycznych zmiennych takie coś jest możliwe btw) - jeśli jest int x = y+1, int y = x+1 to trudno o oczywisty na pierwszy rzut oka wynik.

Ok, teraz @Bumcykowy:

W jaki jeszcze sposób można w C# dokonać inicializacji pól prócz tego sposobu z przykładu pierwszego?

To co napisałeś nie jest inicializajca. W tym wypadku zmienna będzie zainicializowana wartością 0, a następnie zostanie przypisane 4. Chodzi mi o coś na wzór listy inicializujacej w C++.

Jeśli Cię to interesuje, idąc Twoim tokiem rozumowania w C# nie ma w ogóle inicjalizacji: 17.4.5:

The default value initialization described in §17.4.3 occurs for all fields, including fields that have variable
initializers.
Thus, when a class is initialized, all static fields in that class are first initialized to their default
values, and then the static field initializers are executed in textual order
. Likewise, when an instance of a
class is created, all instance fields in that instance are first initialized to their default values, and then the
instance field initializers are executed in textual order
. When there are field declarations in multiple partial
type declarations for the same type, the order of the parts is unspecified. However, within each part the field
initializers are executed in order.

Wszystkie pola są początkowo inicjalizowane domyślnymi wartościami, dopiero później są wypełniane danymi.
Dlaczego? Bo C# to nie C++.
Przypominam że mamy optymalizację i zasady as if. Tak więc nie ma co się przejmować że w jakichśtam przypadkach można dostać wartość 0, skoro i tak kompilator to wytnie.

0

W Javie jest weselej, bo można zrobić tak:

class Something
{
	public int b = getA() + 3;
	public int a = b + 2;
	
	public int getA()
	{
		return a;
	}
}

Zagadka: co się stanie?

0

Dlaczego? IMO, żeby uniknąć zamieszania z circular referencjami (przy statycznych zmiennych takie coś jest możliwe btw) - jeśli jest int x = y+1, int y = x+1 to trudno o oczywisty na pierwszy rzut oka wynik.

Hmm, a w Javie? http://ideone.com/61ICR
Jak widać też nie zrobisz.

Za to C# łyka pętle w staticach (tak jak Java zresztą): http://ideone.com/merLp

W C# tak samo jak w Javie można przekazywać nie do końca stworzone obiekty: http://ideone.com/XQK9E

3

w sprawie inicjalizacji pola w C#.

taki kod:

using System;
using System.Threading;

class Klasa
{
    public int zmienna;
    public Klasa()
    {
        zmienna = 42;
        Console.WriteLine(zmienna);
        Thread.Sleep(2000);
    }
}

class Program
{
    static void Main()
    {
        new Klasa();
    }
}

i taki kod:

using System;
using System.Threading;

class Klasa
{
    public int zmienna = 42;
    public Klasa()
    {
        Console.WriteLine(zmienna);
        Thread.Sleep(2000);
    }
}

class Program
{
    static void Main()
    {
        new Klasa();
    }
}

(Sleep dodany w celach debugowania)

dają dokładnie taki sam kod wykonywalny:

    public Klasa()
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        esi 
    {
        zmienna = 42;
00000004  mov         dword ptr [ecx+4],2Ah 
        Console.WriteLine(zmienna);
0000000b  mov         esi,dword ptr [ecx+4] 
0000000e  call        68B77E94 
00000013  mov         ecx,eax 
00000015  mov         edx,esi 
00000017  mov         eax,dword ptr [ecx] 
00000019  mov         eax,dword ptr [eax+38h] 
0000001c  call        dword ptr [eax+14h] 
        Thread.Sleep(2000);
0000001f  mov         ecx,7D0h 
00000024  call        6A2E0C65 
00000029  pop         esi 
    }
0000002a  pop         ebp 
0000002b  ret
    public int zmienna = 42;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        esi 
00000004  mov         dword ptr [ecx+4],2Ah 
    {
        Console.WriteLine(zmienna);
0000000b  mov         esi,dword ptr [ecx+4] 
0000000e  call        68CF7E94 
00000013  mov         ecx,eax 
00000015  mov         edx,esi 
00000017  mov         eax,dword ptr [ecx] 
00000019  mov         eax,dword ptr [eax+38h] 
0000001c  call        dword ptr [eax+14h] 
        Thread.Sleep(2000);
0000001f  mov         ecx,7D0h 
00000024  call        6A460C65 
00000029  pop         esi 
    }
0000002a  pop         ebp 
0000002b  ret 

czyli nie ma dodatkowej inicjalizacji zerem, a ściślej: nie ma różnicy między jednym a drugim zapisem.

ale jest całkiem prawdopodobne, że w momencie alokacji pamięci pod obiekt, czyli w operatorze new, zerowana jest cała pamięć pod ten nowy obiekt - niezależnie czy i gdzie później inicjalizujemy pola.

2

ale jest całkiem prawdopodobne, że w momencie alokacji pamięci pod obiekt, czyli w operatorze new, zerowana jest cała pamięć pod ten nowy obiekt - niezależnie czy i gdzie później inicjalizujemy pola.

To by się zgadzało ze standardem.

@Azarien - sprawdź może, czy gdybyś nie napisał zmienna = 42 to czy byłoby do niej jawnie przypisywane 0 w konstruktorze?
I jaka byłaby różnica w przypadku kodu podanego na początku (wtedy, kiedy między inicjalizatorem/konstruktorem jest różnica)?

0

Za to to nie chce sie skompilowac

 class Program
    {
        static void Main()
        {
            int zmienna;
            Console.WriteLine(zmienna);

            for (int i; i < 10; i++)
            {
                Console.WriteLine(i);
            }
        }
    } 

Czyli wychodzi na to ze nie jawna inicializajca występuje tylko w polach. Wiec co można nazwać inicializajcą w klasie? Pierwsze przypisanie?

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