Niedozwolone przeciążanie operatora = - jak obejść?

0

Witam,

Na początek kawałek kodu pokazujący co chcę osiągnąć - na tyle proste, że nie trzeba chyba tłumaczyć:

namespace PropertyChange
{
    public partial class Form1 : Form
    {
        Object zmienna;


        public Form1()
        {
            InitializeComponent();

            zmienna = new Object();
            zmienna.OnChange += new Object.ChangedHandler(zmienna_OnChange);
        }

        void zmienna_OnChange(object prevValue, object newValue)
        {
            MessageBox.Show("Zmiana wartości z " + prevValue + " na " + newValue);
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            zmienna.SetValue(10);

        }

        private void button2_Click(object sender, EventArgs e)
        {
            zmienna.SetValue(20);
        }
    
    }

    public class Object 
    {

        public void SetValue(object val)
        {
            Property = val;           
        }
        
        private  object valField = null;
        public delegate void ChangedHandler(object prevValue, object newValue);
        public event ChangedHandler OnChange; 
        object Property
        {
            get
            {
                return valField;
            }

            set
            {
                if (System.Object.Equals(valField, value) == false)
                    OnChange(valField, value);

                valField = value;
            }
        }
    } 
}

Jak widać - klasa Object generuje eventy jeżeli wartość się zmieni - w evencie jest z jakiej wartości na jaką nastąpiła zmiana. Działa to dobrze, ale zamiast metody SetValue i ewentualnie GetValue chcę używać tylko i wyłącznie operatora =, czyli chcę, żeby było możliwe coś takiego:

zmienna = 5;
zmienna =10;  /*Tu wygenerowany event wykrywający zmianę wartości z 5 na 10*/
int a = zmienna;

Jak to zrobić bez możliwości przeciążenia operatora '=' i mając na uwadze, że zapis:

zmienna = 5; 

nie spowoduje wywołania konstruktora z parametrem 5, zapis:

zmienna = new Object(5) 

już tak, ale i tak niewiele to da, bo OnChange nie będzie jeszcze istnieć (wszak przypisywane jest dopiero po wywołaniu konstruktora), więc dostanę wyjątek

Poradzicie mi jakiś trick? A może tego co chcę nie da się zrobić?

0

bez przeciarzenia operatora to chyba nie ma innej mozliwosci. chociaŻ ja sie nie znam ;-)

0

Problem w tym, że w C# przeciążanie operatora = jest niedozwolone. Naprawde nie ma nic w zamian? Bezsens...

0

Zamień pole na właściwość i używaj settera. W nim wywołaj zdarzenie w obiekcie this i masz to co chciałeś.

0

Hmm, a można jaśniej? Bo nie do końca wiem co masz na myśli w tej chwili, proponujesz żeby valField było właściwością? Próbowałem jeszcze przez implicit, ale widzę że nie tędy droga

0

Przeciążanie '=' jest niedozwolone, ale nie znaczy, że nie da się jednego typu rzutować niejawnie do drugiego typu. A można to zrobić np. tak:

public static implicit operator Object(int value)
{
    return new Object(value);
}

// i teraz można:
Object jakis = 5;

Oczywiście jak chcesz, żeby można było rzutować z innych typów, to zamieniasz int na inny typ i odpowiednio go obsługujesz. Można też rzutować w drugą stronę:

public static implicit operator int(Object value)
{
    return Object.ToInt();
}

Powyższe metody umieszczasz w klasie Object

Odnośnie eventa, to proponuje dodać sprawdzanie czy nie jest on pusty:

if (OnChange != null)
    OnChange(valField, value);

I jeszcze dobrą praktyką jest tworzenie event handlerów według ustalonego wzorca, gdzie pierwszy parametr to obiekt, który wywołał dany event, zaś drugi parametr to EventArgs, lub obiekt klasy dziedziczącej po EventArgs. Możesz np. stworzyć taką klasę:

public class ValueChangedEventArgs : EventArgs
{
	public ValueChangedEventArgs(object oldValue, object newValue)
	{
		OldValue = oldValue;
		NewValue = newValue;
	}

	public object OldValue { get; private set; }
	public object NewValue { get; private set; }
}
0
public class Object
{
    private object value;

    public delegate void ChangedHandler(object prevValue, object newValue);
    public event ChangedHandler OnChange;

    public Object(int value)
    {
        this.value = value;
    }

    public void SetValue(object value)
    {
        if(OnChange != null) OnChange(this.value, value);
        this.value = value;
    }

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

private Object foo = new Object(0);
private Object Foo
{
    get { return foo; }
    set { foo.SetValue(value); }
}


public TestForm()
{
    InitializeComponent();
    Foo = new Object(2);

    Foo.OnChange += new Object.ChangedHandler(Foo_OnChange);

    Foo = new Object(10);
    Foo = new Object(15);
}

Chociaż to koślawe trochę. Nie jestem do końca pewny czy obsługa tego nie powinna w całości znaleźć się w klasie nadrzędnej (tam, gdzie będziesz używać tych obiektów).

0

No tak, właśnie pisałem że przyszło mi jeszcze do głowy implicit, ale jest jeden problem. Konstrukcja taka:

public static implicit operator Object(int value)
{
    return new Object(value);
}

spowoduje, że zwracany obiekt będzie mieć pusty event OnChange, a ponieważ metoda jest statyczna, nie można przypisać mu this.OnChange. Co w takim przypadku?

0

W sumie nic konkretnego do głowy mi nie przychodzi, ewentualnie statyczny event/delegate który będzie kopiowany do nowego obiektu w konstruktorze. Myślę, że jest to możliwe, lepsze niż nic i można zmienić event handlera dla danej instancji.

0

Jest to niemożliwe. Dlaczego? .. zobacz co robi taki zapis:

zmienna = 10;

zmienna jest referencją. Referencja ta przed wykonaniem tej linii mogła wskazywać jakiś obiekt (jeśli wcześniej była używana i OnChange zostało przypisane ... lub nie wskazywać - czyli mieć wartość null). To "10" jest stworzone wcześniej, niż przypisanie .. obiekt już istnieje! Przypisanie zmieni obiekt, stare 'OnChange' zginie. A jak już ktoś wspomniał - nie można nadpisać operatora przypisania =.

0

Co robi zapis to wiem. A że jest możliwe, to też wiem. Ale nie jest tak idealne, jakby było przeciążanie operatora '=' albo przynajmniej możliwość uzyskania w argumentach funkcji przeciążającej zarówno wyrażenia po prawej, jak i lewej stronie przypisania, np.:

public static implicit operator Object(Object lval, int rval)
{
	lval.SetValue(rval);
	return lval;
}

i wtedy Object jakis = 5; wywoływałoby wersję jednoargumentową, która tworzyłaby nowy obiekt, zaś kolejne próby przypisania, np. gdzieś dalej w kodzie jakis = 7; wywoływałyby wersję dwuargumentową modyfikując obiekt jakis.

A że jest możliwe proponuję sprawdzić następujący kod:

public class Object
{
	private object value;

	public delegate void ChangedHandler(object prevValue, object newValue);
	public static event ChangedHandler OnChangeStatic;
	public event ChangedHandler OnChange;

	public Object(int value)
	{
		OnChange = OnChangeStatic;
		SetValue(value);
	}

	public void SetValue(object value)
	{
		if (OnChange != null)
			OnChange(this.value, value);

		this.value = value;
	}

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

	public static implicit operator Object(int value)
	{
		return new Object(value);
	}
}

static void Main(string[] args)
{
	Object.OnChangeStatic += new Object.ChangedHandler(Object_OnChangeStatic);
	jakis = 5;
	jakis = 7;
	jakis = 11;
}

void Object_OnChangeStatic(object prevValue, object newValue)
{
	Console.WriteLine(newValue);
}
0

Hmm... dla mnie jako dla kogos, kto przeszedl z C++ jest troche niezrozumiale, dlaczego:

  • nie można przeciążąć operatora '='
  • przeciazenie opoeratora musi byc zrobione za pomocą metody statycznej
0

A że jest możliwe proponuję sprawdzić następujący kod:

Oczywiście, że static można użyć - ale chyba nie o to Ci chodziło. W tej sytuacji jeśli chcesz mieć więcej instancji typu Object - przy wywoływaniu OnChange ie będziesz wiedział, która instancja się zmieniła.

Można dodać parametr Object sender to delegata .. to rozwiąże sprawę - ale marna architektura OOP.

  • przeciazenie opoeratora musi byc zrobione za pomocą metody statycznej

Jak dla mnie to naturalny sposób w jaki można było to zaprojektować. Gdyby była to metoda nie statyczna - operator musiałby działać jedynie w ramach jednej instancji, a nie wszystkich instancji. Ale to już pytanie do projektantów C#....

0

W tej sytuacji jeśli chcesz mieć więcej instancji typu Object - przy wywoływaniu OnChange ie będziesz wiedział, która instancja się zmieniła.
Sam sobie odpowiedziałeś w następnym zdaniu ;)

Można dodać parametr Object sender to delegata .. to rozwiąże sprawę - ale marna architektura OOP.
Lepsza marna niż żadna. OOP to nie jakiś Święty Graal programistów, lecz wzorzec programowania, który zrodził się z chęci programistów do ułatwiania sobie życia. Więc nie widzę sensu, żeby utrudniać sobie życie przy próbie ułatwiania jego. Poza tym statyczne eventy nie są wcale rzadkością we frameworku.
Ja mam podejście do programowania takie, że jak coś jest nie do końca zgodne z ogólnie przyjętymi standardami, wzorcami, paradygmatami itp., lecz wiem dokładnie co robię i to co robię nie przyczyni się do niestabilności kodu, to nie staram się na siłę tego uniknąć. Program ma działać, a nie wyglądać.

0
Deti napisał(a)

operator musiałby działać jedynie w ramach jednej instancji

No i co w tym złego? Każda instancja ma własną metodę przeciążającą operator, z której korzysta, tak było w C++

0

W tej sytuacji jeśli chcesz mieć więcej instancji typu Object - przy wywoływaniu OnChange ie będziesz wiedział, która instancja się zmieniła.
Sam sobie odpowiedziałeś w następnym zdaniu ;)

.. a w trzecim napisałem też, że to rozwiąże problem - nie wiem co chciałeś jeszcze dodać?

Lepsza marna niż żadna. OOP to nie jakiś Święty Graal programistów, lecz wzorzec programowania, który zrodził się z chęci programistów do ułatwiania sobie życia. Więc nie widzę sensu, żeby utrudniać sobie życie przy próbie ułatwiania jego. Poza tym statyczne eventy nie są wcale rzadkością we frameworku.
Ja mam podejście do programowania takie, że jak coś jest nie do końca zgodne z ogólnie przyjętymi standardami, wzorcami, paradygmatami itp., lecz wiem dokładnie co robię i to co robię nie przyczyni się do niestabilności kodu, to nie staram się na siłę tego uniknąć. Program ma działać, a nie wyglądać.

Kwestia podejścia. Ja wole zaprojektować coś od początku do końca dobrze - doświadczenie podpowiada, że właśnie te marnie zaprojektowane rzeczy trzeba przepisywać na nowo jako pierwsze. W tym konkretnym przypadku - zastosowanie statycznego eventa jest jednym z wyjść - ale nie najlepszym wyjściem. Ja bym zrobił property w klasie Obiect, który dla set { } wywołuje event OnChange - wydaje się to najbardziej naturalnym rozwiązaniem.

0

.. a w trzecim napisałem też, że to rozwiąże problem - nie wiem co chciałeś jeszcze dodać?
Zupełnie nic. Zwróciłem tylko uwagę na to, że w jednym zdaniu wskazujesz, że czegoś nie da się zrobić tą metodą, w kolejnym zaś udowadniasz, że da się to zrobić...

Kwestia podejścia.
Oczywiście.

Ja wole zaprojektować coś od początku do końca dobrze
Każdy chce dobrze i każdy stara się projektować najlepiej jak potrafi. Ale chyba nie powiesz, że każde odejście od standardów jest złe? Gdyby Einstein tak myślał, to nadal czas traktowalibyśmy jako stałą. Wzorce są dla programistów, a nie programiści dla wzorców. Wzorce, standardy i paradygmaty istnieją, bo wprowadzają porządek, który jest bardzo ważny przy programowaniu. Ale nie przewidują wszystkich możliwości, chociażby ograniczeń języka i potrzeb programisty. Dobry kod to taki kod, który jest jasny, czytelny i działa tak jak to sobie programista wymyślił.

doświadczenie podpowiada, że właśnie te marnie zaprojektowane rzeczy trzeba przepisywać na nowo jako pierwsze
To chyba raczej zdrowy rozsądek. Doświadczenie może raczej podpowiedzieć czego lepiej nie ruszać w kodzie, aby nie zachwiać delikatnej równowagi programu ;)

W tym konkretnym przypadku - zastosowanie statycznego eventa jest jednym z wyjść - ale nie najlepszym wyjściem
Najlepszym na pewno nie jest. Ale jeśli chcemy mieć koniecznie takie coś:

Obiekt z = 10;
z = 5;

zamiast:

Obiekt z = 10;
z.UstawWartoscJakiegosPolaKlasyObiekt = 5;   // :/ 

lub ewentualnie:

Obiekt z = 10;
z.i = 5;   // ???

No to jest najlepsza metoda. I dopóki mamy pewność, że tylko jeden obiekt będzie używał instancji klasy Obiekt, to w tej metodzie nie ma nic złego.
Ja też bym pewnie w takim przypadku użył właściwości. Ale to nie mój pomysł (przypisywanie int-a utworzonemu już obiektowi, przy zachowaniu eventa), nie mój kod i tylko zakładam, że autor pomysłu potrzebował takiego, a nie innego rezultatu.

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