Laden...

[gelöst] Silverlight: OrderBy mit unbekanntem Datentyp (Reflection)

Erstellt von Easyrider vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.023 Views
E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 12 Jahren
[gelöst] Silverlight: OrderBy mit unbekanntem Datentyp (Reflection)

Hallo,

ich wiedermal. Bin derzeit bei einer Sortierung, wo ich nicht weiterkomme. Als Aufgabe habe ich, eine Liste von Objekten (z. B. als ObservableCollection) nach Eigenschaften aus dem Objekt selbst zu sortieren. Das schwierige daran ist, das ich die Klasse des Objekts selbst nicht zur Verfügung habe. Die Eigenschaften müssen also via Reflection ausgelesen werden.

Hier mal ein bischen Quellcode:

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Person()
        {}

        public Person(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
        public override string ToString()
        {
            return this.Name + " " + this.Age.ToString();
        }
    }

    public class ObjectComparer : IComparer<object>
    {
        public int Compare(object x, object y)
        {
            Person a = (Person)x;
            Person b = (Person)y;

            if (a.Age < b.Age)
                return -1;
            else if (a.Age > b.Age)
                return 1;
            else
                return 0;
        }
    }

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        public IOrderedEnumerable<object> OrderSequence()
        {
            object[] persons = 
            {
                new Person("Christian", 13),
                new Person("Thomas", 20),
                new Person("Stefan", 25),
                new Person("Alex", 1)
            };

            var sorter = GetSorter();
            var sortedNumbers = persons.OrderBy(sorter, new ObjectComparer());

            return sortedNumbers;
        }

        private Func<object, IComparable> GetSorter()
        {
            return o => ((object)o).GetType().GetProperty("Age").Name;
        }

        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {

            IOrderedEnumerable<object> numbers = OrderSequence();

            foreach (object num in numbers)
            {
                this.lblResult.Text += num.ToString() + Environment.NewLine;
            }
        }
    }

Ich habe die eigentliche Aufgabe in ein kleines Beispielprojekt ausgegliedert und den Quellcode dann kopiert.

Die Liste mit den Personen soll nun nach dem Alter der Person sortiert werden. Die Funktion "OrderSequence" darf aber die Klasse "Person" selbst nicht kennen.

Leider funktioniert die Sortierung so noch nicht, da ich andauernd Kompilerfehler wie invalid Arguments oder > Fehlermeldung:

The type arguments for method 'System.Linq.Enumerable.OrderBy<TSource,TKey>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,TKey>, System.Collections.Generic.IComparer<TKey>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. bekomme. Selbst nach vielfachem Suchen in Google und hier konnte ich kein Beispiel finden.

Kann mir jemand von euch erklären, was ich hier falsch mache? 🤔

mfg

Easy

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Easyrider,

vorab: in C# gibt es keine Funktion, nur Methoden die entweder einen Rückgabewerte haben (~Funktion in VB) oder void zurückgeben (~Sub in VB).

Das schwierige daran ist, das ich die Klasse des Objekts selbst nicht zur Verfügung habe.

Woher kommen denn die Objekte? Wenn du darauf Einfluss hast kannst du ja eine "PropertySelector" als Func<object, T> = o => o.Age mitgeben und diesem im Comparer vewenden.
Und was hat das mit dem Titel "generisch" zu tun wenns ganz old-style von .net 1 mit Objekten geht 😃

Sonst würde ich wirklich schauen dass es typisiert stattfindet kann. Alles andere bereitet mehr Probleme als es hilft.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 12 Jahren

Hallo gfoidl,

mit Methoden und Funktion hast du Recht. Das passiert, wenn man zu lange VB programmiert. Bitte das Wording nicht ganz so genau nehmen. 😉

Zum Problem: Meine Objekte kommen von extern. Ich programmiere ein Control in Silverlight, das für die Anzeige der Objekte verantwortlich ist. Dazu gehört, das ich die Objekte gruppieren und sortiern kann. Da das Control aber von unterschiedlichen Anwendungen mit unterschiedlichen Objekten benutzt wird, habe ich keine Chance einen festen Datentyp zu benutzen. Ich weiß, dass genau das das große Problem ist. Aber es ist nunmal Teil der Aufgabe. Leider. 🙁

So etwas wie deinen Property-Selector habe ich drin, siehe GetSorter. Oder meinst du noch etwas anderes?

mfg

Easy

P.S.: Wäre dir der Threadtitel "Silverlight: OrderBy mit unbekanntem Datentyp (Reflection)" lieber? Hab in .Net 1.0 angefangen, somit bin ich hier noch etwas oldschool veranlagt. 8)

5.742 Beiträge seit 2007
vor 12 Jahren

Hallo Easyrider,

kannst du evtl. eine CollectiionViewSource verwenden?

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Easyrider,

das mit dem "Wording" war nur ein gut gemeinter Hinweis, ich hab auch so verstanden was du mit Funktion meinst.

Der neue Titel gefällt mir besser und daher hab ich den auch gleich geändert 😃

Zurück zum Problem:

Dazu gehört, das ich die Objekte gruppieren und sortiern kann.

Das heißt es wird auch erst zur Laufzeit entschieden nach was sortiert/gruppiert werden soll? Dann geht nur der Weg über Reflektion.

Was sich mir aber nicht ganz erschließt ist warum in ObjectComparer dann doch auf Person gecastet wird. Ist die Menge der möglichen Objekt-Typen doch bekannt?
Wenn dem so ist würde ich ein Mapping Typ <-> Comparer erstellen (in einem Dictionary und dann kann von dort einfach der Comparer<T> geladen werden und gut ist es.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 12 Jahren

Das heißt es wird auch erst zur Laufzeit entschieden nach was sortiert/gruppiert werden soll? Dann geht nur der Weg über Reflektion.

Ja, das wird erst zur Laufzeit entschieden. Über ein Kontextmenü hat der Benutzer die Möglichkeit, Gruppierung und Sortierung zu beinflussen (Ausschalten, Spalte Ändern, Sortierreihenfolge). Daher auch der Zwang, es mit Reflection hinzubekommen.

Der feste Cast im ObjectComparer ist nur zu "rationalen" Zwecken vorhanden. Bitte ignorier ihn. In der fertigen Anwendung wird das ganze natürlich über Reflection ausgelesen.

Wenn dem so ist würde ich ein Mapping Typ <-> Comparer erstellen (in einem Dictionary und dann kann von dort einfach der Comparer<T> geladen werden und gut ist es.

Ich gestehe, das ich diesen Satz nicht verstehe. Hast du ein Beispiel dazu, damit ich mir das Wissen aneignen kann?

mfg

Easy

P.S.: @winSharp93: Ich geh die CollectionViewSource gerade durch und schaue, ob ich diese auch anstelle des komplexen Reflection-Zeugs benutzen kann. Thx für den Tip. 😁

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Easyrider,

Hast du ein Beispiel dazu, damit ich mir das Wissen aneignen kann?

Ja siehe:


class Program
{
	static void Main(string[] args)
	{
		Dictionary<string, IComparer> _menuToComparerMapping = new Dictionary<string, IComparer>();
		_menuToComparerMapping["personByAge"] = new PersonByAgeComparer();
		_menuToComparerMapping["personByName"] = new PersonByNameComparer();

		Person p1 = new Person { Name = "A", Age = 2 };
		Person p2 = new Person { Name = "B", Age = 1 };

		string menuEntry = "personByAge";  // wird vom User gewählt - hier nur Demo
		IComparer comparer = _menuToComparerMapping[menuEntry];

		int result = comparer.Compare(p1, p2);
	}
}

public class Person
{
	public string Name { get; set; }
	public int Age { get; set; }
}

public class PersonByAgeComparer : Comparer<Person>
{
	public override int Compare(Person x, Person y)
	{
		return x.Age.CompareTo(y.Age);
	}
}

public class PersonByNameComparer : Comparer<Person>
{
	public override int Compare(Person x, Person y)
	{
		return x.Name.CompareTo(y.Name);
	}
}

Das geht nur wenn bekannt ist dass es Person-Objekt gibt. Wenns weitere bekannte Objekte gibt sind entsprechen mehr Klassen vorzudefinieren.

Wenn nicht bekannt ist welche Objekte existieren, aber bekannt ist dass jedes Objekt eine Name- und Age-Eigenschaft hat dann mittels Reflektion das erweitert/angepasst werden.

Muss kurz weg dann zeig ich dir noch das allgemeine Beispiel mit Reflektion wenn gar nix bekannt ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Easyrider,

hier das Beispiel das nix voraussetzt, außer dass die Eigenschaften IComparable sind.

Angenommen wird dass dann zur Laufzeit die Eigenschaften per Reflektion ausgelesen werden -> der Benutezr kann wählen nach welcher Eigenschaft er sortieren will.


class Program
{
	static void Main(string[] args)
	{
		object[] persons = 
		{
			new Person { Name = "A", Age = 2 },
			new Person { Name = "B", Age = 1 }
		};

		object[] cars = 
		{
			new Car { Power = 2 },
			new Car { Power = 1 }
		};

		// Zur Laufzeit und vom Benutzer gewählt:
		Comparer comparePersonByAge = new Comparer(persons, "Age");
		Comparer compareCarsByPower = new Comparer(cars, "Power");

		Array.Sort(persons, comparePersonByAge);
		Array.Sort(cars   , compareCarsByPower);
	}
}

public class Person
{
	public string Name { get; set; }
	public int    Age  { get; set; }
}

public class Car
{
	public int Power { get; set; }
}

public class Comparer : IComparer
{
	private Func<object, object> _propertySelector;
	private IComparer            _propertyComparer;

	public Comparer(object[] persons, string propertyName)
	{
		PropertyInfo pi   = persons[0].GetType().GetProperty(propertyName);
		_propertySelector = o => pi.GetValue(o, null);

		Type propertyType      = pi.GetGetMethod().ReturnType;
		Type comparerType      = typeof(Comparer<>).MakeGenericType(propertyType);
		PropertyInfo defaultPi = comparerType.GetProperty("Default");
		_propertyComparer      = defaultPi.GetValue(null, null) as IComparer;
	}

	public int Compare(object x, object y)
	{
		object xValue = _propertySelector(x);
		object yValue = _propertySelector(y);

		return _propertyComparer.Compare(xValue, yValue);
	}
}

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 12 Jahren

Hallo,

vielen Dank für das ausgiebige Beispiel gfoidl, jetzt hab ich verstanden was du meinst.

Gelöst habe ich das ganze jetzt mit einer Kombination aus dem Beispiel von dir sowie der CollectionViewSource, dir mir einiges an Arbeit abnimmt.

Danke Jungs 👍

mfg

Easy