Nie całkiem rozumiem, co dokladnie chcesz osiągnąć taką konstrukcją - czy chodzi Ci o utworzenie np. 100 obiektów danej klasy, każdy z buttonem i onclickiem wg. jej definicji, i ta setka buttonow ma sie odnosic do czegos-pojedynczego i wspolnie to cos modyfikowac?
Ogólnie, dobry 'problem' postrzegasz, rozwiązałbyś to w 'tradycyjny, c++owy sposob' poprzez stworzenie w tejze klasie-z-buttonem (niech sie ona zwie klasą Olowek) jakiegos pola & i ustawianie go podczas konstrukcji obiektu. Tutaj się "nie da", gdyz nie mozliwe jest pole typu blah%, mozliwe sa tylko pola blah.
Ale, ale, jesli by to bylo mozliwe. Albo bedac w 'zwyklym' C++, w jaki sposob by to, w ogóle, 'normalnie', miało działać?
Mialbys -cos-, (nazwijmy to klasa Książka, obiekt pantadeusz), to coś zawierałoby jakas zmienna (typ string, nazwa "pierwsza_linijka_pierwszej_strony"). Twoje sto wystapien ołówków teraz musialoby przechowywac -odniesienie- to tegoz ..czego? do tego stringa? stringi sa nie-zmienialne w .Net (no, prawie niezmienialne, ale sza). Pamietanie odniesienia do tego stringa da mozliwosc czytania, ale nie zapisu. W takim razie mozna zapamietac wskaznik-na-niego lub referencje-na-niego, jest to dokladnie to samo, tylko w zapisie inne. Tworzysz wiec obiekt Ksiazka, umieszczasz w zmiennej 'pantadeusz', z tej zmiennej czytasz adres pola 'pierwsza_linijka_pierwszej_strony' i wszystkim olowkom ten adres podajesz. Uaaa teraz moga zapisywac wspolnie!
Ale.. co to robi z "hermetycznoscia"/"hermetyzacja"?
Ktos, olowek, wlasnie poznal -wewnetrzna- konstrukcje obiektow typu Książka? Nie. Nie poznal. Olowek tylko zna "adres-na-string-do-zapisu". Nie ma zielonego pojecia o istnieniu Ksiazki. Tutaj jest w miare ok. Natomiast zupelnie inny kod, ten ktory uzywa ksiazki i buduje olowki, musi nagle obiekt książki "wybebeszac", aż do żywej formy binarnej (jak inaczej pobrac adres?) i stamtad propagowac do olowkow wskazniki, ktore nikt nie wie skad pochodza, jaki maja czas zycia, itp. To bedzie dzialac, ale nie wyglada dobrze i prosi sie o bledy.
W .Net podobnie jak i w Javie, uzywanie "zywych" wskaznikow jest "be". To na pewno juz wiesz, stad sie bierze blah zamiast blah* -- obiekty sa śledzone przez GC, czasem sa przesuwane w pamieci, zeby uniknac jej fragmentacji itp. Co to jest wziecie referencji do pola obiektu? & to zapis uproszczony na ot, kolejny . Referencja do Klasa::podpole to nic innego jak Klasa::podpole, tylko przetlumaczona w jezyku na zapis 'kropkowy' zamiast 'strzalkowy'. Jak wziac referencje do KAWAŁKA obiektu blah ktory moze zostac przez GC przesuniety w inne miejsce pamieci? Niby mozna, ostatecznie skoro blah^ oznacza referencje do obiektu ktory moze byc przesuwany przez GC, to i na pewno dalo byc sie wziac referencje do ten-obiekt-plus-szesc-oczek. Ale nie przewidziano tego **)
Aby uproscic GC, by ten byl jak najszybszy i mial jak najmniej potencjalnych bledow, zminimalizowano rzeczy ktorych GC musi pilnowac. GC pilnuje tylko calych obiektow. Jesli chcesz do czegokolwiek wziac referencje, "odniesienie", to to cos MUSI BYC CALYM OBIEKTEM pilnowanym przez GC.
To daje Ci dwie mozliwosci rozwiazania problemu.
A) Niech klasa Olowek zamiast znac string* do zapisu - niech zna klasę Książka, niech klasa Książka wystawia publiczne cos (metode, wlasnosc, pole, cokolwiek, moze nawet byc owo pole 'pierwsza_linijka_pierwszej_strony' jako publiczne). Od tej pory konstruktor Olowka pobiera Ksiazka^, natomiast gdzies w metodach olowka napiszesz zmienna->pierwsza_linijka_pierwszej_strony = "blah" zamiast *zmienna = "blah".
To jest banalne, zadziala na pewno, ale uszkadza hermetyzacje..
B) Zastosowac to, co Ci Od razu ktos-tam odpisal na forum: OPAKOWAC to pole w mikro obiekcik. Niech stanie sie klasa "WTF" ktora bedzie miala jedno publiczne pole "string napis". Niech Ksiazka od tej pory zamiast "string pierwsza_linijka_pierwszej_strony" ma "WTF pierwsza_linijka_pierwszej_strony". Niech Olowek zamiast brac string* od teraz pobiera "WTF". Obiekty Ksiazka podczas swojej konstrukcji niech robia "pierwsza_linijka_pierwszej_strony=gcnew WTF();". Od teraz olowki konstruujesz podajac im wartosc zmiennej "pierwsza_linijka_pierwszej_strony". Od teraz ksiazka uzywa swojej pierwszej linijki tak: "pierwsza_linijka_pierwszej_strony->napis = "bum"".
Od teraz olowek pamieta odniesienie pojedynczego, calego, obiektu typu WTF zawierajacego jeden string. I przypadkiem ten sam obiekt jest tez uzywany w klasie Ksiazka. Ksiazka wie o calej zawartosci WTF, nie wie o tym ze jest uzywana przez Olowek. Zadna jej czesc nie jest 'wystawiona' publicznie. Wystawiony publicznie jest jakis obiekt WTF ktorego ona uzywa, a nie jej binarne wnetrze. Olowek nie wie o ksiazce, wie ze moze zmieniac zawartosc calego WTF. Nigdzie nie ma wskaznikow ani referencji-do-pol-obiektow. Sa tylko odniesienia do calych obiektow, takie, ktore GC moze i umie sledzic. Wszyscy sa happy, mamy Arkadie, Niebo i Pola Elizejskie w jednym. Osobą nieszczesliwa zostajesz Ty, gdyz musisz napisac 2-3 razy wiecej kodu, zeby wszystkich innych zadowolic na byle 'referencje do stringa'.
O ile prosciej by bylo, gdyby string byl ZMIENIALNY? Przeciez string to tez caly obiekt, sledzony przez GC. String^ wtf = pantadeusz.pierwsza_linijka_pierwszej_strony calkowicie by wystarczyl, Olowki moglyby sobie pamietac te jedna instancje stringa, klasa Ksiazka tez by sobie ja pamietala. Wszyscy uzywali by jednego stringa i go czytali/zmieniali. Mniej kodu, krocej i zwieźlej.
Ale, stringi sa niezmienalne (niby) i kropka. Z "powodow wydajnosciowych i bezpieczenstwa" stwierdzono tak jak w Javie, ze kazda zmiana stringa tworzy nowy string a stary zostaje niezmieniony. Bo podobno ze stringow wiecej sie czyta niz do nich pisze, i podobno tak jest lepiej. Coż.
I z tego powodu, tak jak i w Javie, stringi sa ZMIENIALNE. Tylko Ty tego nigdy NIE ZOBACZYSZ w ten sposob. Dla programisty, string jest nietykalny i tylko do odczytu. Natomiaist jest on normalna klasa, jego bebechy calkowicie normalne i nawet on sam zawiera (niepuliczne) metody pozwalajace go zmieniac. Z tych metod korzysta (podobnie jak w Javie) klasa StringBuilder.
Niech klasa Ksiazka ma pole pierwsza_linijka_pierwszej_strony typu StringBuilder lub StringBuilder. Niech olowek pobiera StringBuidler. Niech wszyscy uzywaja tej klasy do operacji na stringu - sa metody Clear, Append, Replace itp. Jak jest komus potrzebny caly, "poskladany" string - niech sobie wywola stringbuilder->ToString() i dostanie wynikowego stringa *).
I voil'a. Mamy rozwiazanie typu A+B.
Napisalismy minimum kodu, jest pojedyncza zmienna, do niej bierzemy odniesienie normalne ^-kowe.. Zadnych WrapperowNaString z palca, zadnych & czy %.
Ale uzywamy wrappera na string, nazywajacego sie StringBuilder, uzywamy calego obiektu, sledzonego w calosci przez GC.
(defacto, jest to rozwiazanie czysto-B, ale nie Ty piszesz wrapper...)
I tak to powinno wygladac.
Jednak, w momencie gdy eksponujesz NIE-STRING, to oczywiscie nie bedzie juz w systemie klasy "DateBuilder" czy "IntegerBuilder".. Zostana wtedy rozwiazania czyste-A, albo czyste-B.
Ale-Ale, jesli to nie bedzie int, nie string, nie date, nie-cokolwiek-valuetype, tylko jesli bedzie to jakas Twoja KLASA, np. pole bedzie typu "MojaKlasaZPieciomaZmiennymi" --- to juz jestes w domu! Skoro to pole eksponuje OBIEKT, to calosc jego juz jest sledzona i smialo kopiujesz sobie owo MojaKlasaZPieciomaZmiennymi dokadkolwiek "i juz"..
Jedyne problemy beda sprawias STRUKTURY, value-type, int, date, i Twoje struktury. Sledzonej referencji do nich nie wezmiesz, dostaniesz KOPIE, a nie oryginal, trzeba bedzie je opakowywac.. String nie jest value type. String jest klasa, zaprojektowana aby jej obiekty wygladaly jak niezmienialne, wiec dostarczono StringBuilder. Wszystkie int, date itp sa valuetype, sa strukturami, dla nich nie ma builderow.
Tym razem, ponizsze przypisy przeczytaj koniecznie:
*) uwaga. jesli od stringbuildera pobierzesz string, a potem stringbuilderowi kazesz cos zmienic --- MOZE sie okazac, ze ten "niezmienialny" string ktory dostales tez sie zmieni. Niepamietam, ale wydaje mi sie ze stringbuiler dziala na jednym i tym samym obiekcie stringa caly czas. To oznacza, ze po "zakonczonej" budowie stringa, musisz z-re-konstruowac sobie nowy StringBuilder. Sprawdz to koniecznie, np:
Stringbuilder blah;
blah.append "mama";
string x = blah.tostring()
blah.clear
ile teraz wynosi X?
jesli sie wyczysci, to znaczy, ze jednak "StringBuilder" nie pomaga w 100% i ze jesli StringBuildery zyją w Olowkach bardzo dlugo, to musisz bardzo uwazac z tym, co dostajesz z jego ToString i komu tego stringa podajesz dalej.. Albo samemu dbac o to, zeby powstala KOPIA tego stringa co builder zwraca, zeby ja oderwac od nastepnych zmian..
) W C#, w CLI, w .Net istnieje cos takiego jak "pinning", "pin"-nięcie, "przypięcie" obiektu, przybicie gwoździem do miejsca w pamieci. Obiekt z-pin-owany NIE MOZE byc przesuniety przez GC i zawsze (az do un-pin) pozostaje w miejscu. Od tej pory mozna do niego brac ZWYKLE unmanaged wskazniki, Blah = &obiekt, mozna brac & do jego wnetrza, mozna robic wszystko co na zwyklym natywnym obiekcie C++. Minus - zwykle pinning jest scopeowy, abys nigdy nie zapomnial zdjąć gwoździa i nie zostawiał przybitych obiektow. Jesli nie jest (a tu bys potrzebowal..), wprowadza duzo zamieszania w cyklach zycia obiektow i musisz sie dodatkowo narobic aby samemu zadbac o to aby w koncu gwozdzia wyciagnac w dobrym momencie. Zeby nie bylo -- jest jeszcze marshalling i pare innych rzeczy ktore mimo ze siedzisz w .Net i masz GC - pozwalaja na dzialanie na nizszym poziomie i na zabawie z GC w kotka i myszke, ale wtedy z kolei czesciej operujesz na .. IntPtr niz na typ czy typ^, a to juz tak jakbys caly czas pisal uzywajac void*.. czyli lekko kijowo i malo bezpiecznie.
Serio. Rozwiazanie klasy B) albo czysty C++ bez .Net. Ale lepiej zaczac przyzwyczajac do podejscia B), gdyz jesli chcesz pisac 'bezpiecznie' w czystym C++, to tez rozwiazanie B sie czesto stosuje, hermetyzacja jest niestety czesto warta tych paru/nastu linii kodu wiecej
PS.
No tak, zapomnialem - jesli mowa aktualnym .Net :), to sa jeszcze delegaty i funkcje anonimowe. Co to oznacza? Ze Ksiazka ma prywatne pole "linijka", ze Olowek pobiera FUNKTOR/DELEGATA ZMIANY napisu, ze Ksiazka dostarcza FUNKTOR/DELEGATA ZMIANY, ze tworca setki olowkow bierze od panatadeusza ow funktor i podaje go kazdemu olowkowi. Olowki chcac zmienic tresc, biora owa tresc i podaja funktorowi. Czyli wywoluja funktor jak metode i podaja mu tresc w parametrze.. W C++ tez sie to stosuje, jest bardzo, bardzo wygodniejsze niz wrappery.. ale i miejscami bardziej pokrecone i bez odpowiedniej wprawy --- mniej czytelne.