Laden...

Forenbeiträge von jannemann13 Ingesamt 69 Beiträge

26.06.2016 - 11:32 Uhr

Hallo malignate,

vielen Dank, Topologische Sortierung ist genau das Stichwort, das mir fehlte 🙂 Damit komme ich jetzt weiter 👍

25.06.2016 - 19:51 Uhr

Hallo Abt,
hallo malignate,

es ging mir nicht primär um die Struktur, in der ich die Ergebnisliste ablegen kann; dafür ist List<T> meiner Meinung nach ausreichend, wenn ich während der Befüllung sicherstelle, dass ich keine doppelten Einträge aufnehme.

Mein Problem ist der Algorithmus bzw. die Methode, wie ich die Liste erstellen kann. Also quasi, wie bekomme ich eine Liste hin, die alle auftretenden Elemente genau einmal enthält und dabei die Regeln jeder Einzelliste befolgt:

  • 'a' kommt vor 'm'
  • 'm' kommt vor 'p'
  • 'p' kommt vor 'l', 'l' kommt vor 'e'
  • 's' kommt vor 'e'
    usw.

Ich poste hier mal den Code, mit dem ich das im Moment zu erreichen versuche, vielleicht könnt ihr mich damit in die richtige Richtung schubsen 😃

List<List<char>> lists = new List<List<char>> {
	new List<char> { 'l', 'e' },
	new List<char> { 's', 'a' },
	new List<char> { 'a', 'm' },
	new List<char> { 'm', 'p' },
	new List<char> { 'p', 'l', 'e' },
	new List<char> { 's', 'e' },
	new List<char> { 'a' },
	new List<char> { 'p' },
	new List<char> { 's' },
	new List<char> { 'p', 'e' },
	new List<char> { 's', 'a' },
};

int currentItemPosition = 0;
int otherItemPosition = 0;
int otherItem = 0;
char item;
List<char> result = new List<char>();

foreach (List<char> list in lists)
{
	for (int currentItem = 0; currentItem < list.Count; currentItem++)
	{
		Console.WriteLine(string.Format("currentList  : {0}", new string(list.ToArray())));
		Console.WriteLine(string.Format("currentItem  : {0}", list[currentItem]));

		if (!result.Contains(list[currentItem]))
			result.Add(list[currentItem]);

		if (currentItem < list.Count - 1)
		{
			otherItem = currentItem + 1;
			otherItemPosition = result.IndexOf(list[otherItem]);
			currentItemPosition = result.IndexOf(list[currentItem]);
			if (otherItemPosition != -1 && currentItemPosition > otherItemPosition)
			{
				for (int offset = currentItemPosition; offset < list.Count; offset++)
				{
					item = result[offset];
					result.RemoveAt(offset);
					result.Insert(otherItemPosition + offset, item);
				}
			}
		}

		Console.WriteLine(string.Format("Zwischenstand: {0}", new string(result.ToArray())));
		Console.ReadLine();
	}
}

Console.WriteLine(string.Format("Endergebnis: {0}", new string(result.ToArray())));
Console.ReadLine();
25.06.2016 - 15:41 Uhr

Hallo Abt,

danke für die schnelle Antwort. Ich habe das Beispiel konstruiert und dabei die Listen intuitiv so sortiert, dass es mit einfachem Hinzufügen funktionieren würde, sorry. Die Listen können aber auch "später" noch neue Einträge enthalten, die in der Ergebnisliste weiter vorne liegen müssen:

List<List<char>> lists = new List<List<char>> {
	new List<char> { 'a', 'm' },
	new List<char> { 'm', 'p' },
	new List<char> { 'p', 'l', 'e' },
	new List<char> { 's', 'e' },
	new List<char> { 'p', 'e' },
	new List<char> { 's', 'a' },
};

So bekomme ich erst bei der 4. Einzelliste die Information, dass vor dem 'e' noch ein 's' einsortiert werden muss, dann wäre ich an der Stelle bei einem Zwischenstand { 'a', 'm', 'p', 'l', 's', 'e' }.

Dann in der 6. Liste erfahre ich, dass das 's' sogar noch vor dem 'a' liegen muss und käme dann erst auf das Endergebnis { 's', 'a', 'm', 'p', 'l', 'e' }.

Ich hoffe, das ich das verständlich erklärt habe 😃

(edit: Typos)

25.06.2016 - 15:11 Uhr

Hallo,

ich habe hier ein Problem, das einfach klingt, aber an dem ich mir grad ein wenig die Zähne ausbeisse 😃 Ich möchte mehrere Listen zu einer Gesamtliste zusammenfassen, aber so, dass kein Eintrag doppelt vorkommt und die Reihenfolge der Einträge aus den Einzellisten erhalten bleibt. Hier ein Beispiel:

List<List<char>> lists = new List<List<char>> {
	new List<char> { 's', 'a' },
	new List<char> { 'a', 'm' },
	new List<char> { 'm', 'p' },
	new List<char> { 'p', 'l', 'e' },
	new List<char> { 's', 'e' },
	new List<char> { 'p', 'e' }
	new List<char> { 'm' }
};

Das Ergebnis soll die Sequenz { 's', 'a', 'm', 'p', 'l', 'e' } sein. In diesem Beispiel gibt es keine "widersprüchlichen" Angaben wie etwa { 'e', 's' }, das kann aber in meiner Anwendung durchaus vorkommen. In dem Fall würde ich das gerne feststellen und abbrechen, weil dann keine eindeutige Reihenfolge definiert ist.

Mir fehlt hier der Ansatz, wie ich die Reihenfolge der Einträge untereinander vergleichen, in die Ergebnislliste übernehmen und dabei noch Fehler feststellen kann. Gibt es hierfür eine einfache Methode oder hat jemand einen Tipp für mich?

Vielen Dank schonmal!

13.01.2014 - 16:22 Uhr

Hallo Angandi,

Ich nehme an, du vertauschst jetzt eine Property einer Klasse mit der Definition einer Enumeration? Du brauchst ja noch eine Property vom Typ "Farbe", um da etwas zuweisen zu können:


public class Dose
{
        public enum Farbe
        {
            Rot= 0,
            Blau = 1,
            Grün = 2,
        }

        public Farbe MeineFarbe { get; set; }

        public Dose(Farbe farbe) 
        {
                this.MeineFarbe = farbe;
        }

}

02.08.2013 - 12:05 Uhr

Hallo Gordrin,

das klingt, als ob du nach einer eigenen Property mit Indexzugriff suchst: Indexers Tutorial (MSDN)

18.06.2013 - 12:39 Uhr

Hallo Manuals,

vielleicht noch ein Tipp: So selbstgemachte Scheduler-Komponenten können schnell mal komplizierter werden (überschneidende Zeiten, Feiertage, Zeitumstellungen, Ausführungsverzögerungen, ...). Schau dir mal Quartz.NET an, das ist ein ziemlich umfangreicher Scheduler. Warum das Rad neu erfinden? 😉

25.09.2012 - 09:37 Uhr

Hallo theYoRecords,

Im Hintergrund einen Thread laufen zu lassen der andauernd nichts anderes macht als prüfen strapaziert das System nur unnötig und ist unter Umständen auch nicht allzu genau..

Wieso zu ungenau, wie exakt müssen die Ereignisse denn ausgelöst werden? Und so ein Thread müsste doch nicht viel mehr tun als einmal pro Minute zu checken, ob ein Termin/Alarm vorliegt und dann ein Event feuern, das deine Anwendung dann behandelt. Das würde ich nicht "strapazieren" nennen.

24.09.2012 - 13:08 Uhr

versuch mal die Eigenschaft "View" auf "Details" zu setzen

Das funktioniert leider auch nicht, wie herbivore schon sagt, das kann man scheinbar nur zu Fuss machen.

@planetberlin: Vielleicht kannst du statt des ListViews ein DataGridView verwenden? Damit werden auch Zellen mit CheckBoxen "ab Werk" in der Hintergrundfarbe der Zeile gezeichnet.

31.08.2012 - 10:02 Uhr

Hallo raphaelgl,

Schau mal hier: Convert numbers with exponential notation from string to double or decimal

Die anschliessende Formatierung für die Ausgabe kannst du dann zum Beispiel per String.Format() erledigen.

17.08.2012 - 14:09 Uhr

Der sieht für mich vollkommen in Ordnung aus. Mir ist nur aufgefallen, dass ich mit einem Array weniger ausgekommen bin. "_magnitude1" ist ja quasi bis auf ein/eins in "_lessThan20" enthalten.

Ja stimmt, das hätte man noch etwas kürzen können ... wären aber wieder mehr Spezialfälle geworden 🙂

Du bist dran!

OK, der Thread deckt ja jetzt schon eine Menge ab und mir fällt grade keine wirklich neue Aufgabe ein, also machen wir doch das Naheliegende 🙂

Und nochmal rückwärts

Erstelle einen Parser, der aus einer komplett ausgeschriebenen Zahl den passenden Zahlenwert ermittelt. Das Ergebnis sollte eine Methode mit folgender Signatur sein:

int TextToNumber(string numberText);

Beispiele:

Eingabe "Eins" --> Ausgabe: 1
Eingabe "Zweiundvierzig" --> Ausgabe: 42
Eingabe "Acht Millionen Zweihundertachtunddreissigtausendvierhundertsechsundneunzig" --> Ausgabe: 8238496

Der Rahmen für die gültigen "Zahlentexte" soll auch hier wieder 0 bis 1 Milliarde umfassen, also genau wie in der letzten Aufgabe, nur eben umgekehrt. Zum Erzeugen von Testeingaben kann dann praktischerweise meine Klasse oder tribs Code verwendet werden ... Viel Spass 8)

16.08.2012 - 21:34 Uhr

Hallo trib,

Hier mal meine Lösung, basierend auf deinen Tipps:

public class NumberToTextConverter
{
  private string[] _lessThan20 = new string[]{ "null", "eins", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun", "zehn", "elf", "zwölf", "dreizehn", "vierzehn", "fünfzehn", "sechzehn", "siebzehn", "achtzehn", "neunzehn" };
  private string[] _magnitude1 = new string[] { "", "ein", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun" };
  private string[] _magnitude10 = new string[] { "", "zehn", "zwanzig", "dreissig", "vierzig", "fünfzig", "sechzig", "siebzig", "achtzig", "neunzig" };
  private string[] _magnitudesSingular = new string[] { "hundert", "tausend", " million ", " milliarde " };
  private string[] _magnitudesPlural = new string[] { "hundert", "tausend", " millionen ", " milliarden " };

  public string NumberToText(int number)
  {
    StringBuilder resultBuilder = new StringBuilder();
    string result = string.Empty;
    string numberText = number.ToString();
    int orderOfMagnitude = 0;

    if (number < 20)
      result = _lessThan20[number];
    else
    {
      while (numberText.Length > 2)
      {
        result = this.NumberPartToText(numberText.Substring(numberText.Length - 3, 3), orderOfMagnitude++) + result;
        numberText = numberText.Remove(numberText.Length - 3);
      }
      result = this.NumberPartToText(numberText.PadLeft(3, '0'), orderOfMagnitude++) + result;
    }

    foreach (string word in result.Split(' '))
      if (word.Length > 0)
        resultBuilder.Append(word.Substring(0, 1).ToUpper() + word.Substring(1) + " ");

    return resultBuilder.ToString(); 
  }

  private string NumberPartToText(string numberPart, int orderOfMagnitude)
  {
    string result = string.Empty;

    if (numberPart == "000")
      return string.Empty;

    if (numberPart == "001" && orderOfMagnitude > 0)
      return (orderOfMagnitude == 1 ? "ein" : "eine") + _magnitudesSingular[orderOfMagnitude];

    if (numberPart[0] != '0')
      result = _magnitude1[int.Parse(numberPart[0].ToString())] + _magnitudesSingular[0];

    result += this.TwoDigitsToText(numberPart.Substring(1, 2));
    if (orderOfMagnitude > 0)
      result += int.Parse(numberPart) > 1 ? _magnitudesPlural[orderOfMagnitude] : _magnitudesSingular[orderOfMagnitude];

    return result;
  }

  private string TwoDigitsToText(string twoDigits)
  {
    string result = string.Empty;
    int value = int.Parse(twoDigits);

    if (twoDigits == "00")
      return string.Empty;

    if (value < 20)
      return _lessThan20[value];

    if (twoDigits[1] != '0')
      result += _magnitude1[int.Parse(twoDigits[1].ToString())] + "und";
    result += _magnitude10[int.Parse(twoDigits[0].ToString())];

    return result;
  }
}

Viel kürzer krieg ich's aufgrund der ganzen Spezialfälle nicht hin ... schon erstaunlich, wie "unregelmässig" das ist, wenn man genau hinschaut 🙂 Zum Testen hab ich folgenden Code genommen:

static void Main(string[] args)
{
  NumberToTextConverter num = new NumberToTextConverter();
  int[] test = new int[] {
    0, 1, 19, 30, 99, 
    100, 201, 311, 420, 543,
    1000, 2001, 3012, 4300, 5430, 5432, 
    10000, 20001, 30012, 55555, 99200,
    100000, 200001, 333333, 550055, 444000, 333301,
    1000000, 2000001, 1234567, 5000000, 7050011, 
    10000000, 30000001, 12345678, 87654321, 1000000000, int.MaxValue };

  for (int i = 0; i < test.Length; i++)
    Console.WriteLine("{0}: {1}", test[i].ToString().PadRight(10), num.NumberToText(test[i]));

  Console.ReadLine();
}

Und die Ausgabe sieht in dem Fall so aus (ob die Spaces da jetzt überall Sinn machen, weiss ich nicht, aber das wäre ja schnell geändert - und vorgegeben war es auch nicht):

0         : Null
1         : Eins
19        : Neunzehn
30        : Dreissig
99        : Neunundneunzig
100       : Einhundert
201       : Zweihunderteins
311       : Dreihundertelf
420       : Vierhundertzwanzig
543       : Fünfhundertdreiundvierzig
1000      : Eintausend
2001      : Zweitausendeins
3012      : Dreitausendzwölf
4300      : Viertausenddreihundert
5430      : Fünftausendvierhundertdreissig
5432      : Fünftausendvierhundertzweiunddreissig
10000     : Zehntausend
20001     : Zwanzigtausendeins
30012     : Dreissigtausendzwölf
55555     : Fünfundfünfzigtausendfünfhundertfünfundfünfzig
99200     : Neunundneunzigtausendzweihundert
100000    : Einhunderttausend
200001    : Zweihunderttausendeins
333333    : Dreihundertdreiunddreissigtausenddreihundertdreiunddreissig
550055    : Fünfhundertfünfzigtausendfünfundfünfzig
444000    : Vierhundertvierundvierzigtausend
333301    : Dreihundertdreiunddreissigtausenddreihunderteins
1000000   : Eine Million
2000001   : Zwei Millionen Eins
1234567   : Eine Million Zweihundertvierunddreissigtausendfünfhundertsiebenundsechzig
5000000   : Fünf Millionen
7050011   : Sieben Millionen Fünfzigtausendelf
10000000  : Zehn Millionen
30000001  : Dreissig Millionen Eins
12345678  : Zwölf Millionen Dreihundertfünfundvierzigtausendsechshundertachtundsiebzig
87654321  : Siebenundachtzig Millionen Sechshundertvierundfünfzigtausenddreihunderteinundzwanzig
1000000000: Eine Milliarde
2147483647: Zwei Milliarden Einhundertsiebenundvierzig Millionen Vierhundertdreiundachtzigtausendsechshundertsiebenundvierzig
02.11.2011 - 12:52 Uhr

Hallo Georg,

das klingt für mich so, als würde die Anwendung mit einer nicht gefangenen Exception abbrechen. Fängst du diese via Application.ThreadException bzw. AppDomain.CurrentDomain.UnhandledException bereits ab? Falls nicht, behandle diese beiden Events mal, da kommen manchmal Fehler an, mit denen man nicht gerechnet hat 😃

20.05.2011 - 22:44 Uhr

Ich finde es ein wenig "gemein", dass man hier diese Vorgehensmodelle als "akademischen Quatsch" abtut. Mal ehrlich: hat sich hier im Vorhinein noch NIEMAND mal Gedanken über Klassendesign o.ä. gemacht?

Ich tue die Vorgehensmodelle ja nicht generell als Quatsch ab - schliesslich hab ich sie im Studium auch gelernt und ihren (theoretischen) Nutzen auch erkannt. Ich bin auch der Meinung, dass man, wenn man diese Modelle grundsätzlich kennt und versteht, automatisch effektiver entwickelt als wenn nicht. Es ist nur in der beruflichen Praxis oft so, dass es nicht immer leicht zu vermitteln ist, dass es sich später lohnen kann, am Anfang mehr Zeit zu investieren und gründlich zu planen. Besonders Entscheidern, die nicht aus der Entwickler-Praxis kommen, fehlt meiner Erfahrung nach oft das Verständnis dafür - oder mir fehlt das BWL-mässige Verständnis dafür, warum es besser ist, schnell was auf den Markt zu hauen, auch wenns sich später höchstwahrscheinlich rächen wird 😃

Bei mir ist es jedenfalls auch so, wie oben schon jemand geschrieben hat: wenn ich privat etwas programmiere, dann nehme ich mir Zeit, um mir ein für mich optimales Konzept zurechtzulegen und lege extremen Wert darauf, dass ich immer wieder neuere Techniken/Varianten/Konzepte ausprobiere und umsetze. Ist vielleicht auch so eine Art Kompensation zum Beruf, und so verkommt so manches Projekt dann auch gerne mal zum Selbstzweck, weil ich ewig an der "perfekten" Implementierung feile ... aber so bleibt man halbwegs auf dem Laufenden und lernt dazu, auch wenn diese Projekte natürlich deutlich kleiner und isolierter sind als im Job.

Mal ehrlich: hat sich hier im Vorhinein noch NIEMAND mal Gedanken über Klassendesign o.ä. gemacht?

Das ist halt das Problem mit Bestandssoftware - da hat man nun den Salat und muss irgendwie damit zurechtkommen. Ganz frische Projekte quasi auf der grünen Wiese starten, das hab ich eher selten erlebt. Ist natürlich der Traum jedes Entwicklers, mal ein grosses Projekt völlig ohne Einschränkungen und Zeitdruck ganz neu zu entwerfen ...

Habt ihr nen Boss, der euch Aufgaben vorsetzt - "implementiere das", die Software soll so und so aussehen (Softwardesign, nicht Oberflähe)?

Eher so: "Der Kunde hat die und die Anforderungen, hier ist dein Ansprechpartner, denk dir mal was dazu aus und schätz den Aufwand. Oh, und bitte nicht mehr als 15 MT, mehr kriegen wir nicht bezahlt.". Es gibt hier selten konkrete Vorgaben, was das Softwaredesign angeht, dafür sind wir ja hier als Entwickler beschäftigt 😃 Damit läuft es letztendlich wohl in etwa darauf hinaus, was du als Continuous Integration beschreibst.

Das konkrete Vorgehen läuft dann bei uns meist so: Ein Entwickler ist für das neue Feature/die Anforderung zuständig (normalerweise der, der sich am besten im entsprechenden Teil des Systems auskennt). Der werkelt dann eine Weile still vor sich hin und checkt das Ganze ein, wenn er meint, dass er fertig ist. Das wars ... dann wird die Doku (Handbuch, ChangeLog, Entwicklerdoku) aktualisiert, evtl. gibts ne kurze Info an die anderen Entwickler, und dann gehts ab zum Kunden (bei den "wichtigen" Kunden wird vorher schon noch ein kompletter Testlauf gemacht, soweit das möglich ist, bei kleineren Sachen eher nicht). In den folgenden Tagen werden dann äusserst "agil" die auftretenden Bugs behoben, bis das ganze stabil läuft und vom Kunden abgenommen wird.

Überlegungen macht man natürlich. Aber zu sehr ins Detail gehen ist auch hier meistens zu früh, dazu sollte erst mal ein bisschen was stehen.

Das seh ich genau so. Wenn ich eine neue Komponente entwerfe, mache ich mir natürlich vorher einen Plan (nicht formal, eher Notizen/Skizzen auf Papier oder im Kopf, schicke UML-Diagramme mach ich meist nur für Präsentationen oder im Nachhinein für die Doku). Aber sobald dann die erste lauffähige Version steht, habe ich schon wieder eine lange Liste mit Refactorings, die ich gerne durchführen würde, um die Wartbarkeit/Übersichtlichkeit/Erweiterbarkeit/etc. zu erhöhen. Aber die kosten natürlich reichlich Zeit, und dazu kommen meist noch Last-Minute-Änderungen an den Anforderungen, Missverständnisse mit dem Kunden, unbedachte Schnittstellen zu anderen Systemen, etc. Und das nächste Projekt wartet ja auch schon ...

19.05.2011 - 23:55 Uhr

Daher dürfte es kein Felher sein, wenn man das tut was gerade nötig ist. Egal wie mans nennt 😉

So läuft das auch bei uns. Ob man das jetzt im akademischen Sinne "agil" nennen kann oder nicht, mag ich nicht festlegen. Wir entwickeln hier in einem kleinen Team (5-7 Entwickler) und es gibt letztendlich kein formal vorgeschriebenes Vorgehensmodell. Wir haben eine relativ alte Software-Suite im Einsatz (seit etwa 10 Jahren), die permanent am Leben gehalten und erweitert werden muss und planen/entwickeln seit einiger Zeit an einer Neuentwicklung, die sie "demnächst" ablösen soll. Ich weiss nicht, ob dir das wirklich weiterhilft, aber praktisch gelebt wird das bei uns so (die Punkte sind nach Priorität sortiert, der wichtigste oben):

1.) Wenn ein Kunde eine Anforderung hat, für die er auch bezahlen mag, wird das mit Priorität so quick wie möglich & dirty wie nötig umgesetzt ("Vergiss die Wartbarkeit der alten Software, bald machen wir eh alles neu und besser")
2.) Wird ein Bug bekannt, wird er zügig behoben. Oft genug notdürftig mit irgendwelchen hart-kodierten Hacks, bei denen sich einem eigentlich die Zehennägel hochrollen sollten - aber Hauptsache, es geht wieder sigh
3.) Gibt es Anforderungen, die nicht direkt bezahlt werden, kommen die auf eine Liste und werden bei Zeiten umgesetzt. Diese Liste ist ziemlich lang, und nur die allerkleinsten Änderungen kommen immer mal wieder in die Releases.
4.) Tests (im Sinne von organisierten Unit- oder Integrationstests unter Beteiligung dedizierter Tester) gibt es aus Zeit- und Personalmangel selten bis gar nicht. Meistens "testet" der zuständige Entwickler in der IDE, der Rest wird beim Kunden gemacht.
5.) Falls neben den Punkten 1-4 (und der entsprechenden Dokumentation) Zeit bleibt, wird an der Konzeption der neuen Software und deren Umsetzung gearbeitet - leider sehr oft unterbrochen durch die Punkte 1-4 und daher wenig effektiv ...

Das ist also das "Modell", nach dem bei uns entwickelt wird. Nachdem ich bereits zuvor bei zwei anderen Firmen ähnliche Erfahrungen gesammelt habe und von dem was ich so höre/lese, vermute ich stark, dass das auch als 'verbreitetes Modell' durchgehen kann. Die Praxis weicht eben oft vom akademischen Ideal ab, um den Kunden/Chef schnell zufrieden zu stellen. Und die Hoffnung bleibt, dass man in der neuen Version alles organisierter, ordentlicher und damit besser machen kann ...

Vielleicht noch als Schlusswort: Auch wenn das hier vielleicht (für dein Anliegen) etwas negativ klingen mag, macht mir die Arbeit doch Spass und ist ziemlich abwechslungsreich. Es ist zwar bei Zeiten mal etwas stressig und man muss sich des Öfteren auf die Finger beissen, weil man genau weiss, dass man gerade mal wieder bewusst gegen X Regeln der professionellen Softwareentwicklung verstösst, trotzdem lernt man eine Menge dabei. Und letztendlich funktioniert das System irgendwie, wir bekommen es immer irgendwie hin, unsere Aufgaben zu erledigen und die Anforderungen zu erfüllen.

Ich hoffe, es gibt noch weitere Antworten, denn mich interessiert es auch sehr, wie in anderen Unternehmen bei der Softwareentwicklung praktisch vorgegangen wird. Ansonsten: Viel Erfolg bei deiner Abschlussarbeit 😃

22.02.2011 - 14:19 Uhr

Machst du die Initialisierung des WCF-Service in deiner Main-Methode?

Nein, in OnStart().

22.02.2011 - 13:58 Uhr

Hallo,

ich habe hier ein Problem mit einem WCF-Service, das mich gerade ziemlich vor ein Rätsel stellt:

Der WCF-Service soll als Windows-Dienst in einer VM (Windows Server 2003 Standard SP2) laufen. Über das lokale Netz sollen sich mehrere Clients verbinden und den Service nutzen können. Entwickelt/getestet hatte ich den Service zunächst in einer Konsolenanwendung als Host, weil das Debuggen so einfacher ist. Der Service wurde mit .Net 3.5/VS 2008 entwickelt und verwendet ein einfaches NetTcpBinding für den einzigen Endpoint. Soweit der Plan.

Als der WCF-Service dann funktionierte und stabil lief, wollte ich ihn in einen Windows-Dienst "umziehen". Ich habe also einen Dienst erstellt, der den Service exakt genauso startet wie die Konsolenanwendung, und ihn mit InstallUtil installiert. Das funktionierte, ich konnte den Dienst starten und sehe auch an den Logs, die er schreibt, dass die Serverlogik hinter dem WCF-Dienst korrekt arbeitet. Versuche ich jetzt aber, mit meinem TestClient eine Verbindung herzustellen, bekomme ich die Fehlermeldung, dass keine Verbindung zum EndPoint hergestellt werden kann (EndpointNotFoundException, "Es konnte keine Verbindung mit "net.tcp://MeinRechner:12345/MyService" hergestellt werden. Der Verbindungsversuch hat für einen Zeitraum von 00:00:00.9375000 angedauert. TCP-Fehlercode 10061: Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte").

Starte ich nun ohne jede Änderung wieder den Konsolen-Host, funktioniert alles wunderbar, der TestClient verbindet sich sofort mit dem Service und kann ihn fehlerfrei nutzen. Ich habe jetzt schon eine Weile versucht, der Ursache für das Problem auf die Spur zu kommen, bin aber im Moment etwas ratlos. Vielleicht könnt ihr mir ja helfen ... folgendes habe ich bereits versucht, bzw. einfach zur Info:
*netstat zeigt bei der Konsolenanwendung den offenen Port an, beim Dienst nicht *Der Service wird definitiv mit der gleichen Konfiguration bzw. dem gleichen Code gestartet wie die Konsolenanwendung *Es läuft keine Firewall o.Ä., die eine TCP-Verbindung verhindern würde (weder auf meinem PC noch auf der Ziel-VM) *Der ServiceName ist gesetzt, es gibt keine anderen Anwendungen auf "meinem" Port und laut EventViewer (und meinen Logs) startet der Dienst ohne Fehler *Ich habe den Dienst als LocalSystem, NetworkService und LocalService ausprobiert - keine Auswirkung *Sogar wenn die Konsolenanwendung auf der Ziel-VM mit einem User komplett ohne Rechte (nur RDP) starte, funktioniert der Service und die Verbindung des TestClients *Ob ich den Endpoint im Code oder per app.config definiere, macht keinen Unterschied

Der Code für die Erstellung des ServiceHosts sieht (in beiden Fällen) so aus:

_host = new ServiceHost(typeof(MyService));
_host.AddServiceEndpoint(
  typeof(IService),
  new NetTcpBinding(),
  "net.tcp://MeinRechner:12345/MyService");
_host.Faulted += new EventHandler(ServiceHost_Faulted);
_host.Open();

Das ganze habe ich jeweils auf der Ziel-VM und meinem PC (Windows XP SP3) ausprobiert, auf beiden System verhält es sich gleich - der Endpoint scheint im Windows-Dienst einfach nicht zu funktionieren.

Also, ich wäre für jeden Hinweis dankbar ... wenn ihr noch weitere Informationen oder Codeausschnitte braucht, sagt bitte einfach Bescheid, ich wollte das Post hier nicht unnötig lang machen - vielleicht gibts ja auch eine ganz einfache Lösung dafür 😃

Vielen Dank schonmal!

19.01.2010 - 09:08 Uhr

was gefällt dir an Jacks Methode genau nicht?

Ich würde für jede Standard-Exception eine eigene Methode benötigen. Die Beispielmethode in meinem Post oben ist auch nur eine von (momentan) vier verschiedenen Überladungen, auch die müsste ich dann einzeln für jeden Typ haben. Oder ich müsste beim Aufruf den Typ der gewünschten Exception mit übergeben ... ich finde es so einfacher zu lesen und es ist kürzer.

Ich persönlich finde, das ist hier die bestgenannteste Methode, besonders wenn du später auf MultiLingual setzen möchtest, dann hast du alle Exception genau an einer Stelle.

Das ist richtig, allerdings ist das Projekt bereits multilingual und Standard-Exceptions werden - wenn sie eben nicht über die Zusatzinfos verfügen - bereits an anderer Stelle zentral mit Texten in der aktuellen Sprache versehen, falls sie dem User angezeigt werden müssen.

19.01.2010 - 08:47 Uhr

Schau dir doch mal das PropertyGrid an. Damit kann man recht einfach und automatisch eine Oberfläche für alle öffentlichen Properties eines Objekts erzeugen ... das ist natürlich in der Optik nicht so flexibel wie eine selbst gemacht Form, aber eben schnell und standardgemäß 😃

18.01.2010 - 17:03 Uhr

So, jetzt bin ich zufrieden 😃

Da ich das ganze ja sowieso nur benötige, wenn ich selbst irgendwo eine Exception auslösen möchte, habe ich jetzt statt der Konstruktoren eine Erweiterungsmethode WithDetails() geschrieben:

public static Exception WithDetails(this Exception ex, string description, params ErrorDetail[] details)

(ErrorDetail ist im Prinzip wie ein Dictionary<string, object>.)

Dieser Methode (und einigen Überladungen) kann ich nun alle Zusatzinfos mitgeben, nach dem Schema:

throw new ApplicationException("Kurze Fehlermeldung!", innerException)
  .WithDetails("Ausführliche Fehlerbeschreibung.",
               new ErrorDetail("Detail1", detailInfo1),
               new ErrorDetail("Detail2", detailInfo2),
               new ErrorDetail("Detail3", detailInfo3));

So kann ich immer den passenden Standard-Exceptiontyp verwenden und habe trotzdem alles ordentlich lesbar in einem Aufruf. Danke nochmal für den Tipp, JAck30lena.

Ich markier den Thread mal als gelöst, auch wenn das nicht 100%ig auf das Topic passt.

18.01.2010 - 13:03 Uhr

Das ist ne gute Idee 😃 So hab ich die eigene Klasse dann komplett raus ... damit probiere ich mal ein wenig herum, danke!

18.01.2010 - 12:54 Uhr

und wie wäre es denn mit einer art factory?

Das hatte ich auch schon überlegt, aber irgendwie ... Kanonen und Spatzen und so ...

Nein, an sich funktioniert das so, und ich will ja so nah wie möglich an der Standard-Exception bleiben, damit auch Standard-Exceptions vom Fehleranzeige-System möglichst gleich behandelt werden können. Daher nutze ich ja auch .Data für alle zusätzlichen Informationen, anstatt eigene Properties an eigene Exception-Klassen anzuhängen.

18.01.2010 - 12:45 Uhr

Wird zwar nicht gerne gesehen, aber erstelle eine neue Exception-Klasse und leitete von der betroffenen ab. Den neuen Konstruktor kannst du anschließend gestalten wie du möchtest.

Meinst du jetzt, dass ich von jeder Standard-Exception, die ich benötige, eine Ableitung schreiben soll, nur um die zusätzlichen Konstruktoren zur Verfügung zu haben?

Momentan habe ich eine einzelne abgeleitete Exception, die die Konstruktoren enthält und sonst nichts ... hat natürlich den Nachteil, dass dann der Name der Exception nicht wirklich aussagekräftig sein kann (aber notfalls gibts ja noch die InnerException). Ist grundsätzlich so auch OK, weil die eigene Exception ja eh nur dann ausgelöst wird, wenn ich zusätzliche, aussagekräftige Daten habe, für die ich die zusätzlichen Konstruktoren ja benötige.

15.01.2010 - 13:46 Uhr

für solche fälle gibt es in c# 3.0 den object-initializer.

Hm ja, aber in meinem Fall ginge dann der Komfort drauf, das ganze wie eine 'normale' Exception zu verwenden, denke ich. In den zusätzlichen Konstruktoren werden teilweise relativ komplexe Objekte und Listen in Exception.Data geschrieben - das müsste ich dann jedesmal komplett beim Aufruf zusammenstellen.

15.01.2010 - 11:19 Uhr

Ja, macht irgendwie Sinn. Schade, trotzdem danke für die schnelle Antwort 😃

15.01.2010 - 11:10 Uhr

Hallo,

ich habe eine kurze Frage: Kann man einer existierenden Klasse irgendwie einen weiteren Konstruktor hinzufügen, ohne eine abgeleitete Klasse zu erstellen? Quasi einen Konstruktor als extension method?

Hintergrund: Ich habe einige extension methods für System.Exception geschrieben, die auf Daten zugreifen, die in Exception.Data abgelegt sind. Momentan habe ich eine eigene Exception-Klasse, die von System.Exception ableitet und nur ein paar Konstruktoren beinhaltet - alles andere habe ich in den extension methods untergebracht. Nun würde ich gerne auf die eigene Exception-Klasse verzichten, aber den Komfort der diversen Konstruktoren behalten ... geht das irgendwie?

Danke schonmal 😃

21.10.2009 - 00:09 Uhr

Hallo TripleX,

... mittels ^ auf das gewünschte Style umändere (z.B. Bold). Da der markierte Text insegesamt (bis auf ein Wort) aber normal formatiert ist, macht er eine xor verknüpfung ...

Wenn du statt XOR ein OR nimmst, sollte es wie gewünscht funktionieren --> |-Operator

20.10.2009 - 21:38 Uhr

Ok, dann das erklärt den Typ-Parameter. Aber auch das funktioniert doch im oben genannten Beispiel, solange das Interface die entsprechende getDataContext() vorschreibt und deine Table-Klasse sie implementiert ... wo genau liegt denn jetzt eigentlich das Problem ?(

20.10.2009 - 21:21 Uhr

Ich glaube, wir reden da etwas aneinander vorbei 😃

Ich gehe jetzt einfach mal davon aus, dass du aus Gründen, die nichts mit diesem Problem hier zu tun haben, die Klasse TableConnector generisch machen musst/willst.

Hier kann ich keinen "Interface" Typen einfügen sondern muss spezifizieren das, dass eingefügte Element die Eigenschaften class, DBTable, new() hat.

Aber dann kannst du doch trotzdem (oder grade weil du ja festgelegt hast, dass der Typ 'TableType' das Interface 'DBTable' implementieren muss) in der Methode die Insert-Methode der Tabelle aufrufen?

public class TableConnector<TableType>
  where TableType : class, DBTable, new()
{
  public long insertData(TableType insertEntity)
  {
    return insertEntity.Insert();
  }
}

Das funktioniert bei mir wunderbar mit den Voraussetzungen aus meinem letzten Post (natürlich wenn Insert() statt void einen long-Wert zurückgibt). Und wenn ich den Constraint weglasse, gibts natürlich den Fehler, dass TableType die Methode Insert() nicht kennt ... aber den Fehler, den du nennst, kann ich so nicht nachvollziehen.

(Edit: Typos...)

20.10.2009 - 20:55 Uhr

Ok, danke erstmal für eure Hilfe soweit. Ich habs jetzt nochmal etwas anders umgesetzt, mit einem Dictionary statt einem einfachen Wert als Property. Das funktioniert jetzt so wie bisher, spart mir aber die ganzen Properties in den abgeleiteten Klassen:

Die Basisform und eine Ableitung:

public class FormTemplate : Form
{
  // Hier speichere ich jetzt die Logos für die einzelnen Forms,
  // analog dazu dann ein Dictionary für jede frühere Wert-Property:
  public static Dictionary<Type, Image> ApplicationLogo { get; set; }
  // ...
  
  // Im Konstruktor kann ich dann über den Typ des Objekts den 
  // passenden Wert übernehmen:
  public FormTemplate()
  {
    picApplicationLogo.Image = FormTemplate.ApplicationLogo[this.GetType()];
    // ...
  }
}

// Jetzt brauche ich für diese Funktion gar nichts mehr in der abgeleiten Klasse:
public class ErrorForm : FormTemplate { }

Dann kann ich in allen Anwendungen an zentraler Stelle die Werte zuweisen, und die Forms danach ohne Weiteres verwenden ...

FormTemplate.ApplicationLogo.Add(typeof(ErrorForm), Resources.Logo);
FormTemplate.ApplicationLogo.Add(typeof(MessageForm), Resources.Logo);
FormTemplate.ApplicationLogo.Add(typeof(AboutForm), Resources.Logo);
// ...
ErrorForm oErrorForm = new ErrorForm();
oErrorForm.ShowDialog();

... und habe so immer die passenden Werte je nach Anwendung und Klasse. Die abgeleiteten Klassen selbst merken nichts davon bzw. sind an der ganzen Aktion nicht beteiligt. Ich hake den Thread mal als gelöst ab, wenn es aber noch Verbesserungsvorschläge oder andere Ideen gibt, bitte gerne posten 😃

20.10.2009 - 20:14 Uhr

Ja, das geht tatsächlich nur so, wie herbivore schreibt. Es gibt da leider kein 'fertiges' Event, um dazwischen zu gehen, das hat mich auch schon eine Weile beschäftigt 😃

Im Prinzip brauchst du einen eigenen TypeConverter (die relevante Methode zum Überschreiben ist dabei ConvertFrom(), da kannst du eingreifen) und weist diesen dann den Properties mittels TypeConverterAttribute zu.

20.10.2009 - 20:05 Uhr

Tut mir leid, ich verstehe immer noch nicht, wofür die Typ-Parameter gebraucht werden. Wenn das dein Interface ist (nur mal als Beispiel):

public interface DBTable
{
  void Insert();
}

Und das zwei Implementierungen (die von mir aus auch in verschiedenen Assemblies liegen können):

public class Tabelle1 : DBTable
{
  public void Insert() { /* Implementierung für Tabelle1 */ }
}
public class Tabelle2 : DBTable
{
  public void Insert() { /* Implementierung für Tabelle2 */ }
}

Dann könntest du doch einfach über das Interface auf die entsprechenden Methoden zugreifen, egal, was das jetzt genau für eine Klasse ist, oder wo sie her kommt:

public class TableConnector
{
  public void Insert(DBTable table)
  {
    table.Insert();
  }
}
20.10.2009 - 18:23 Uhr

Wie gesagt, die Forms unterscheiden sich in ihrem Inhalt und ihrer Verwendung so deutlich, dass ich anfangs nicht an Vererbung gedacht habe. Was sie gemeinsam haben, sind einige Controls, Form-Vererbung bietet sich also an und funktioniert auch prima, quasi als 'Template'. Ich muss mich nur noch um den Forminhalt kümmern, der ganzen Corporate-Identity-Kram drumherum wird in der Basisform abgewickelt.

Das funktioniert so lange wunderbar, wie ich das Ganze nur in einer Anwendung verwende. Ich kann z.B. im Konstruktor der abgeleiteten Klassen fest ein anderes Icon angeben oder ich lege das in einer privaten Konstante fest oder oder oder ...

Jetzt habe ich aber eine zweite Anwendung, die dieselben Forms mit anderen Icons oder Logos (oder meinetwegen Texten in der StatusBar o.Ä.) verwenden will - ich muss also bei der Erstellung eines Form-Objekts wissen, welche Anwendung hier grade läuft und welches Icon jetzt gewünscht ist --> Ich muss die Information jedes Mal mitteilen.

Habe ich statische Properties für diese Werte (jetzt auf den abgeleiteten Klassen, so wie es gerade umgesetzt ist), lege ich in jeder Anwendung einmal fest, mit welchem Satz Icons/Logos/Texten eine Klasse arbeiten soll, und muss mich bei der Objekterstellung um nichts mehr kümmern.

Sieht also so aus, als müsste ich mich entscheiden, wo ich die Redundanz haben will - beim Erzeugen der Objekte oder im Code der Klassen.

Gib der Base eine Map, die standardmäßig leer ist. Möchtest du nun einen nicht-statischen Wert setzen, speicherst du diesen in der Map. Das hat zur Folge, das du bei jeder get-Methode die Map prüfen musst --> höherer Rechenaufwand.

Hm ja. Sowas ähnliches war mir auch schon in den Sinn gekommen. Aber wie du schon sagst, das ist eigentlich ziemlich viel Aufwand für dieses 'Problem'.

20.10.2009 - 18:02 Uhr

Eigentlich ist das schon der passende Ansatz, wie du ihn oben beschrieben hast - Interface vereinbaren, und die Klassen, die das Interface implementieren, zur Verarbeitung anbieten/verwenden. Bis hier hin brauchst du dafür auch erstmal keine Generics. Vielleicht kannst du folgenden Satz aus dem OP nochmal etwas genauer erklären, damit ich verstehe, warum du Typ-Parameter verwendest?

Die Typargumente benötige ich weil das Interface ja abstrakt ist, ich aber Einfügeoperationen nur mit konkreten Klassen vornehmen kann.

Bis dahin verstehe ich das so: Dein Interface legt fest, dass die Methode 'processTable()' implementiert werden muss. Du hast ein paar Klassen in deiner DLL, die das auch tun. Dann hast du irgendwo deine Verarbeitungsmethode, die ein Objekt des Typs 'Dein Interface' erwartet und dessen 'processTable'-Methode aufruft.

20.10.2009 - 17:43 Uhr

In diesem Beispiel würde ein Objekt von Dialog1 immer das allgemeine Logo liefern, Dialog2 immer das spezielle. Aber eben auch in jeder Anwendung. Ich habe die ganzen Forms in einer DLL, und jede Anwendung muss ihnen irgendwie sagen können, welchen Wert eine Eigenschaft haben soll.

Es gibt aber auch eigentlich kein 'allgemeines Logo' in diesem Sinn - jede abgeleitete Klasse hat im Prinzip ein Anderes. Es soll sich nur immer an der selben Stelle befinden und anwendungsweit gelten. Aus diesem Grund hatte ich zuerst auch nicht an Vererbung gedacht, und die Eigenschaften auf allen Klassen platziert. Dann fiel mir auf, dass das ziemlich viel doppelter Code ist, der ausgelagert werden sollte.

20.10.2009 - 17:34 Uhr
processAttributeTable<aktType, aktType.getFurtherTable()>(aktType);  

Also erstmal müssen Typ-Parameter immer beim Kompilieren bekannt sein. Auch wenn aktType.getFurtherTable() einen Typ zurückgibt, wird das so nicht funktionieren. Ist das denn die selbe Methode wie im ersten Post (processAttributeTable <-> processTable)? Wenn ja, erwartet sie doch einen Parameter vom Typ 'TableType', du übergibst aber aktType, einen Type ... irgendwas stimmt da noch nicht ganz 😃

20.10.2009 - 17:22 Uhr

Schon klar, in meinem Fall ist es aber die Regel, dass die Implementierung (hier: der Wert) abweicht und nicht die Ausnahme - im Grunde habe ich nur einen immer gleichen Satz von Variablen, die ich auf jeder Klasse mit anderen Werten fülle - aber eben auf der Klasse, nicht den Einzelobjekten.

Vielleicht muss ich die ganze Struktur nochmal neu überdenken. Da gibts sicher noch einen einfacheren, schöneren Weg.

20.10.2009 - 17:13 Uhr

Dann könntest du z.B. auch mit Settingsobjekten arbeiten.
Oder eben mit den abstrakten / virtuellen Properties.

Hm. Ich verstehe vermutlich nicht, wie du das meinst. Wenn ich auf der Basisklasse virtuelle/abstrakte Properties vorgebe, muss ich sie überschreiben/implementieren. In diesem Fall (einfache Properties ohne weitere Logik) sehe ich den Vorteil nicht, dann kann ich auch gleich die Properties direkt in den Spezialisierungen angeben.

Was den verlinkten Artikel angeht: das ist ein interessanter Ansatz, aber die Form-Vererbung erfüllt meinen Zweck soweit eigentlich hervorragend. Das 'Problem' ist ja nur die Reduzierung von Codewiederholungen.

20.10.2009 - 16:49 Uhr

Eine Möglichkeit wäre, das ganze umzudrehen (mehr Aufwand, aber IMHO sauberer): Eine Form, die verschiedene UserControls hostet, und sozusagen den "Rahmen" vorgibt.

Kannst du das etwas näher erklären? Was meinst du mit 'Rahmen vorgeben' und wozu in diesem Fall UserControls?

Ansonsten weiß ich nicht, warum du das ganze als statische Properties implementieren willst - abstrakt bzw. virtuelle Properties bzw. direktes Setzen im Konstruktor ist doch deutlich angenehmer.

Weil ich in jeder betroffenen Anwendung eine zentrale Initialisierungsmethode habe, an der ich diese Forms alle einmalig konfigurieren möchte. Ich möchte dann eigentlich nicht an jeder Stelle, an der eine Instanz der Forms tatsächlich verwendet wird, etwas im Konstruktor übergeben müssen - ändern sich irgendwann die Anforderungen, muss ich jeden Aufruf anpassen, so nur die einmalige 'Konfiguration' beim Start der Anwendung.

Oder - fällt mir selbst grad beim Schreiben auf - meinst du einfach "warum überhaupt der ganze Quatsch, erzeug dir pro Anwendung je eins der Formulare und verwende sie dann weiter"? Naja ... normalerweise verwende ich lieber statische Methoden als globale Objekte während der gesamten Laufzeit der Anwendung dabei zu haben. Aber das ist in dieser Situation vermutlich wirklich sinnvoller ...

20.10.2009 - 16:09 Uhr

Stelle die statische Property einfach in jeder abgeleiteten Klasse bereit.

Das mache ich ja im Moment, aber genau das würde ich eben gerne vermeiden, weil ich so in diversen Klassen den selben Code habe und bei Änderungen überall manuell ran muss.

Was ist denn dein eigentliches Ziel? So ganz optimal klingt die Vorgehensweise nicht.

Es geht um eine Basisklasse für verschiedene Forms. Die Basisklasse bietet quasi einen einheitliches Layout mit Firmenlogo, Icon, Überschrift, Statusleiste etc. Diverse andere Forms sind von dieser Form abgeleitet, benötigen jedoch teilweise andere Werte in den geerbten Eigenschaften. Als Beispiel:

public class BaseForm : Form
{
  public static Image ApplicationLogo { get; set; }
  public static Image FormIcon { get; set; }
  // ...
}

public class ErrorForm : BaseForm { /* ... */ }
public class AboutForm : BaseForm { /* ... */ }
// ...

Jetzt möchte ich das Ganze aber in mehreren Anwendungen verwenden, die auf den Forms immer ein eigenes ApplicationLogo anzeigen:

// Die ApplicationLogos sind z.B. innerhalb einer Anwendung immer gleich:
ErrorForm.ApplicationLogo = Resources.logo;
AboutForm.ApplicationLogo = Resources.logo;

// Aber die Icons können sich je nach Form unterscheiden:
ErrorForm.FormIcon = Resources.icon_error;
AboutForm.FormIcon = Resources.icon_info;

Und andere Anwendungen weisen an dieser Stelle eben ihr eigenes Logo zu und verwenden möglicherweise auch andere Icons. Ich hoffe, das ist etwas klarer beschrieben als oben ... und ich nehme ja auch an, dass das deutlich einfacher geht, aber ich steh grad wohl auf dem Schlauch 😛

20.10.2009 - 15:51 Uhr

Hallo Riker81,

  
public void processTable<TableType, HistoryType>(TableType dataTable)  
            where TableType : class, "MeinInterface", new()  
            where HistoryType: class, "MeinInterface2", new()  
//Mein Interface1 hat eine Methode die ein Objekt von Typ MeinInterface2   
//zurückgibt  
  

Ich nehme an, du hast die Anführungszeichen im echten Code nicht drin? So kompiliert das jedenfalls nicht, Constraints auf Typ-Parametern müssen immer entweder einen Typ, new(), struct oder class angeben (oder halt einen anderen verwendeten Typ-Parameter).

Ich verstehe das Problem noch nicht ganz, glaube ich. Wie genau äussert sich denn der Fehler?

20.10.2009 - 15:27 Uhr

Hallo,

Ich habe hier ein Designproblem, und würde euch gerne um Vorschläge bitten, wie man das sinnvoll lösen kann:

Das Ursprungsproblem: Ich möchte einige statische Eigenschaften einer Basisklasse für jede abgeleitete Klasse seperat setzen. Das geht natürlich nicht, weil ich mir ja jedes Mal die Eigenschaft mit dem neuen Wert überschreibe:


public class BaseClass
{
  public static string Bla { get; set; }
}

public class Derived1 : BaseClass { /* ... */ }
public class Derived2 : BaseClass { /* ... */ }

Zuweisung der Werte:


Derived1.Bla = "String1";
Derived2.Bla = "String2"; <-- Überschreibt natürlich auch Derived1.Bla, weil statische Property auf der Basisklasse.

Diese Eigenschaft muss statisch sein, weil ich jeder abgeleiteten Klasse einmalig und anwendungsweit einen Wert zuweisen möchte, der dann für alle später erzeugten Objekte gilt. Blöderweise habe ich aber mehrere abgeleitete Klassen, die genau die selben Eigenschaften mit anderen Werten benötigen. Wie kann man diese Anforderung auflösen, ohne die Eigenschaften doch wieder auf jeder abgeleiteten Klasse zu implementieren (so hab ich es im Moment notgedrungen gemacht)?

Vielen Dank schonmal!

10.10.2009 - 11:37 Uhr

Ok, das liegt an dem Zugriff auf den Browser durch den anderen Thread, siehe z.B. hier, dort steht auch, wie man das richtig macht.

Du könntest aber auch einfach (wie oben schon gesagt) das DocumentCompleted-Event des Browsers abwarten und dann den SourceCode auslesen (dann entfällt auch das Thread.Sleep()), z.B. so:

private void Form1_Load(object sender, EventArgs e)
{
  webBrowser2.DocumentCompleted += webBrowser2_DocumentCompleted;
  webBrowser2.Navigate("http://www." + swradio + ".fm/");
}

void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
  string roflmao = webBrowser2.DocumentText;
  MessageBox.Show(roflmao);
}
10.10.2009 - 10:40 Uhr

Ich habe gute Erfahrungen mit einfachen Textmarken als Platzhalter für einzelne 'Felder' im Word-Template gemacht, die lassen sich einfach über COM-Interop befüllen.

Für Tabellen mit dynamischer Grösse würde ich auch empfehlen, die komplett im Code zu erzeugen und dann (ebenfalls z.B. über eine Textmarke) in das Dokument einzufügen.

10.10.2009 - 10:26 Uhr

Vielleicht kannst du mal den Code-Ausschnitt posten, in dem der Fehler auftritt - irgendwo löst du da einen ungültigen Cast aus.

09.10.2009 - 20:55 Uhr

Du definierst zuerst eine Enumeration mit den gewünschten Werten:

public enum DataType
{
  Boolean,
  Int32,
  String
}

Und Legst dann auf dem UserControl eine Eigenschaft dieses Typs an:

public class MyLabel : Label
{
  public DataType DatenTyp { get; set; }
}

Dann kannst du im Designer aus den verfügbaren Werten auswählen.

09.10.2009 - 12:49 Uhr

Hallo,

(das ist jetzt doch ein ziemlich langes Post geworden, aber ich weiss nicht, wie ich das kürzer erklären kann. Ich hoffe, es ist halbwegs verständlich geblieben.)

In meiner Anwendung gibt es eine Komponente, die Daten von verschiedenen Servern lädt, die ich nur über COM-Komponenten ansprechen kann. Unter Angabe verschiedener Parameter kann diese Datenzugriffsschicht dann die verschiedenen Datenobjekte abfragen, speichern, löschen, zählen, Listen erzeugen etc. Für jede dieser Funktionen gibt es teilweise mehrere Methoden, die alle ähnlich arbeiten, aber unterschiedliche Rückgabetypen und/oder Übergabeparameter haben.

Das Ganze funktioniert, und aus Sicht des Anwenders dieser Komponente ist sie auch sehr einfach und intuitiv zu verwenden. So weit, so gut - das Problem ist aber, dass der Code für die eigentliche Komponente extrem lang ist und sich in grossen Teilen vielfach wiederholt. Bei Änderungen am Ablauf/der Struktur gibt es also leider viele Stellen, die berücksichtigt werden müssen. Der 'Daten-Code' selbst ist hierbei nicht mal der Hauptverursacher, sondern vielmehr Verbindungsaufbau, Fehlerbehandlung und Aufräumlogik. Im Moment sieht eine solche Methode z.B. so aus:


// Kurz zur Erklärung: Diese Methode lädt ein einzelnes Objekt vom Server,
// das den Bedingungen des Parameters where entspricht. Der Typparameter T
// ist der Typ des zu ladenden Objekts.
protected T Load<T>(Where where)
  where T : StorageObjectBase<T>, new()
{
  T oResult = default(T);
  QueueBrowseSet oBrowseSet = null;
  bool bRetry = false;
  int iRetries = this.MaxRetries;

  do 
  {
    try
    {
      if (this.Connect())
      {
        // Hier passiert die eigentliche Aktion. Die hier verwendeten Methoden 
        // haben keine eigene Fehlerbehandlung, damit hier alles zusammenläuft.
        oBrowseSet = this.CreateBrowseSet<T>(this.ExtractWorkSpace(typeof(T)), this.ExtractQueueName(typeof(T)), where, 1);
        oResult = this.CreateObjectFromBrowseSet<T>(oBrowseSet);
		bRetry = false;
      }
      else
      {
        // Verbindungsfehler behandeln ...
      }
    }
    catch (COMException ex)
    {
      // COM-Exceptions behandlen ...
      bRetry = this.HandleComException(ex);
    }
    catch (Exception ex)
    {
      // Alle weiteren Exceptions behandeln ...
      bRetry = this.HandleException(ex);
    }
    finally
    {
      // Verbindung trennen, wenn erwünscht:
      if (!this.ConnectionKeepAlive) this.Disconnect();

      // COM-Objekte aufräumen:
      if (oBrowseSet != null) 
      {
        Marshal.FinalReleaseComObject(oBrowseSet);
        oBrowseSet = null;
      }
    }
  } while (bRetry);

  return oResult;
}

Wie ihr seht, ist die eigentliche Arbeit in diesem Fall mit 2 Zeilen getan. Da ich hier jedoch mit COM-Objekten arbeiten muss, fällt der ganze Kram drumherum ziemlich gross aus. Ich habe hier so ca. 12 Methoden in diesem Stil, die sich alle nur geringfügig unterscheiden, aber dann eben z.B. List<T> zurückgeben, T-Objekte odder Listen speichern oder andere Parameter haben. So. Ich hoffe, dass noch jemand bis hier hin mitgelesen hat, denn jetzt kommen meine Fragen:
*Nur mal das 'Gerüst' dieser Methode an sich betrachtet: Ist eine solche Struktur sinnvoll? Kann man sowas evtl. kürzer oder einfacher lösen?

*Ich würde gerne den generellen Aufbau dieser Klasse/Komponente ändern, so dass ich diese Fehlerbearbeitung, Verbindungsprüfung und Aufräumarbeiten zentral an einer Stelle habe, und letztendlich nur noch die wirkliche Aktion (z.B. die 2 Zeilen im Try oben) an eine solche Methode übergebe. Ich sehe da 2 Möglichkeiten:

1.Delegates: Bietet sich an, ich bin aber nicht sicher, ob/wie sich das umsetzen lassen würde, wenn man bedenkt, dass ich ja fast für jede der jetzigen Methoden einen eigenen Delegatentyp bräuchte. Ich habe auch noch nicht viel Erfahrung mit der Verwendung von Delegates, und bevor ich da jetzt viel Zeit investiere, würde ich gerne vorher abschätzen können, ob sich das lohnen wird.

1.Ein Interface und ein 'Action'-Objekt. Da war mein Ansatz folgender:

public interface ISafeExecutable
{
  bool Execute();
  void CleanUp();
}

// Das 'Action'-Objekt, dass die Arbeit ausführt:
public class StorageAction<T> : ISafeExecutable
{
  public T ResultObject { get; protected set; }
  public T ResultList { get; protected set; }
  public long ResultCount { get; protected set; }

  public bool Execute()
  {
    // Hier würde ich dann z.B. abhängig von einem Flag oder so
	// die verschiedenen Methoden ausführen und bei Erfolg immer
	// true zurückgeben. Der eigentliche Rückgabewert wird dann
	// in eine der Properties oben geschrieben.
  }
  
  public void CleanUp() 
  {
    // COM-Objekte freigeben, wenn welche verwendet wurden.
  }
}

Und dann wieder in der Datenzugriffskomponente:

// Die zentrale Aufrufmethode würde dann z.B. so aussehen:
protected bool SafeExec(ISafeExecutable method)
{
  bool bRetry = false;
  bool bResult = false;
  int iRetries = this.MaxRetries;

  do
  {
    try
    {
	  if (this.Connect())
      {
        bResult = method.Execute();
      }
      else
      {
        // Verbindungsfehler behandeln ...
      }
    }
    catch (COMException ex)
    {
      // COM-Exceptions behandlen ...
      bRetry = this.HandleComException(ex);
    }
    catch (Exception ex)
    {
      // Alle weiteren Exceptions behandeln ...
      bRetry = this.HandleException(ex);
    }
    finally
    {
      // Verbindung trennen, wenn erwünscht:
      if (!this.ConnectionKeepAlive) this.Disconnect();

      // COM-Objekte aufräumen:
      method.CleanUp();
    }
  } while (bRetry);

  return bResult;
}

// Und der Aufruf selbst dann so:
protected T Load<T>(Where where)
  where T : StorageObjectBase<T>, new()
{
  StorageAction<T> oAction = new StorageAction<T>();
  if (this.SafeExec(oAction))
    return oAction.ResultObject;
  else 
    return null;
}

Das sieht im Vergleich zum Ist-Zustand schon ganz gut aus, finde ich, aber wie ihr seht, werden noch keine Parameter an die StorageAction übergeben. Auch die Sache mit den Rückgabeobjekten gefällt mir noch nicht wirklich ... aber ich denke, man sieht, worauf ich hinaus will: eine zentrale Methode, die den ganzen 'Wrapperkram' abwickelt und einfach die Arbeitsaufgabe übergeben bekommt und dann ausführt.

Wie lässt sich sowas möglichst flexibel umsetzen?

08.10.2009 - 18:04 Uhr

Als praktische Extension für System.String ginge das z.B. so:

public static string GetBetween(this string sourceString, string startTag, string endTag, bool removeTags)
{
  string sResult;
  int iStartTagLength = 0;
  int iEndTagLength = 0;

  if (removeTags)
    iStartTagLength = startTag.Length;
  else
    iEndTagLength = endTag.Length;

  sResult = sourceString.Substring(sourceString.IndexOf(startTag) + iStartTagLength);
  sResult = sResult.Substring(0, sResult.IndexOf(endTag) + iEndTagLength);
  return sResult;
}
07.10.2009 - 15:19 Uhr

Funktioniert denn die Änderung zur Laufzeit?

Ich dachte eigentlich, ja. Bis mir dann klar wurde, dass ich die Eigenschaft (einfacher bool-Wert) zur Laufzeit gar nicht mehr ändere. Wenn ich das dann tue, ändert sich auch zur Laufzeit die Optik nicht. Mittlerweile hab ich die Lösung aber gefunden, durch schrittweises Debuggen im .Net-Framework selbst. So sieht die Eigenschaft jetzt aus:

private bool _bAlwaysShowValidationInfo;
public bool AlwaysShowValidationInfo
{
  get { return _bAlwaysShowValidationInfo; }
  set {
    if (_bAlwaysShowValidationInfo != value)
    {
      _bAlwaysShowValidationInfo = value;
      this.RecreateHandle();  // <-- Das ist der Aufruf, der mir fehlte :-)
    }
  }
}

Letztendlich muss zum Neuzeichnen wohl das Handle des Controls neu erzeugt werden, und das geht eben mit Control.RecreateHandle(). Jetzt wird das Control zur Laufzeit und auch im Designer komplett neu gezeichnet, sobald die Eigenschaft geändert wird.

06.10.2009 - 22:09 Uhr

Das hilft leider auch nicht - habe auch bereits testweise diverse this.Invalidate()-Aufrufe in verschiedenen Events verstreut, von denen ich dachte, sie würden vom Designer ausgelöst (werden sie vermutlich auch, aber ich kriege keine entsprechende Rückmeldung beim Debuggen mit .DesignMode).

Irgendwie muss das ja gehen, bei anderen Properties funktionierts ja auch. Ich vermute, da fehlt ein Attribut auf der Property. Ich habe auch mal beim Debuggen ein bisschen im .Net-Sourcecode nachgeschaut, ohne Ergebnis bisher.