I have below class object structure with having three inner objects and i am trying to check that only one child object should have the data other wise throw an error and i can do this with if- else statements but looking to see any other way to implement this
public class GlazingOrGasMaterial
{
public GlazingComplexMaterial GlazingComplexMaterial { get; set; }
public GlazingSimpleMaterial GlazingSimpleMaterial { get; set; }
public GlazingGasMaterial GlazingGasMaterial { get; set; }
public GlazingOrGasMaterial(GlazingOrGasMaterial layer)
{
if (layer.GlazingComplexMaterial != null && layer.GlazingGasMaterial != null && layer.GlazingSimpleMaterial == null)
{
throw new ArgumentException("exception for more than one object having data");
}
// more if statements
}
}
and i am calling this constructor like as in below
foreach(var layer in input.Layers)
{
GlazingOrGasMaterial glazingOrGasMaterial = new GlazingOrGasMaterial(layer);
}
Could any one please let me know any way to do this escaping bunch of if-else statements, many thanks in advance
Actually, according OOP, other guys are right, you had better change your class strategy. But I don't know the whole task so I'm able to give an advice only.
I also provided another solution of your question according to your requirements:
public GlazingOrGasMaterial(GlazingOrGasMaterial layer)
{
BitArray materialBits = new BitArray(new bool[] { layer.GlazingComplexMaterial != null, layer.GlazingGasMaterial != null, layer.GlazingSimpleMaterial != null });
byte[] array = new byte[1]; // max number of states depends on the type: byte - 8, short - 16 etc.
materialBits.CopyTo (array, 0);
byte materialState = array[0];
if((materialState != 0) && ((materialState & (materialState - 1)) == 0)) //check if only 1 property has a data
throw new ArgumentException("exception for more than one object having data");
else
//do what you wish
//an example, using by switch you could recognize the exactly not null property
}
How about creating a collection from your objects and counting the ones which meet your expectation:
public GlazingOrGasMaterial(GlazingOrGasMaterial layer)
{
if (new object[] { layer.GlazingComplexMaterial, layer.GlazingSimpleMaterial, layer.GlazingGasMaterial }.Count(x => x != null) != 1)
{
throw new ArgumentException("Expected only one material");
}
...
}
I think you could also consider using a different data modeling approach to enforce this requirement - for example, you could make all of the materials inherit from abstract BaseMaterial and then have the "container" class (if you'd still need it) with only one BaseMaterial property instead of 3 separate ones.
I suggest you change the shape of your class to enforce the 1 material rule. If all the possible materials extend from a common material, then you can create this base class and only allow one instance.
public class GlazingOrGasMaterial
{
public Material _material { get; set; }
public GlazingOrGasMaterial(Material material)
{
_material = material;
}
}
public class Material
{
}
public class GlazingComplexMaterial : Material
{
}
public class GlazingSimpleMaterial : Material
{
}
public class GlazingGasMaterial : Material
{
}
You can then instantiate the class with one of the materials without having to worry that there are multiple. This avoids runtime exceptions!
void Main()
{
var material = new GlazingComplexMaterial();
new GlazingOrGasMaterial(material);
}
This also avoids the problem in the original design where a 2nd or even 3rd material can just be assigned, without going through the constructor validation logic.
Related
I have a class ScoreStrategy that describes how to calculate points for a quiz:
public class ScoreStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
[Required]
public Quiz Quiz { get; set; }
public decimal Correct { get; set; }
public decimal Incorrect { get; set; }
public decimal Unattempted { get; set; }
}
Three properties Correct, Incorrect and Unattempted describe how many points to be assigned for a response. These points can also be negative. The score strategy applies to all questions in the quiz, thus there can only be one ScoreStrategy per quiz.
I have two subclasses:
public class DifficultyScoreStrategy : ScoreStrategy
{
public QuestionDifficulty Difficulty { get; set; }
}
public class QuestionScoreStrategy : ScoreStrategy
{
[Required]
public Question Question { get; set; }
}
My questions have three difficulty levels(Easy, Medium, Hard; QuestionDifficulty is an enum). The DifficultyScoreStrategy specifies if points for questions of a specific difficulty need to be assigned differently. This overrides the base ScoreStrategy that applies to the entire quiz. There can be one instance per difficulty level.
Thirdly, I have a QuestionScoreStrategy class that specifies if points for a specific question have to be awarded differently. This overrides both the quiz-wide ScoreStrategy and the difficulty-wide DifficultyStrategy. There can be one instance per question.
While evaluating the responses of the quiz, I want to implement a level-by-level fallback mechanism:
For each question:
Check if there is a QuestionScoreStrategy for the question and return the strategy if one is found.
If not, fallback to DifficultyScoreStrategy and check if there is a strategy for the difficulty level of the question being evaluated
and return it if a strategy is found.
If not, fallback to the quiz-wide ScoreStrategy and check if one exists and return it if it does,
If there is no ScoreStrategy either, use default as { Correct = 1, Incorrect = 0, Unattempted = 0 }(It would be great if I can make this configurable as well, something much like the .NET's elegant way:
options => {
options.UseFallbackStrategy(
correct: 1,
incorrect: 0,
unattempted: 0
);
}
).
Summary
I've summarized the above info in a table:
Strategy Type
Priority
Maximum instances per quiz
QuestionScoreStrategy
1st (highest)
As many as there are questions in the quiz
DifficultyScoreStrategy
2nd
4, one for each difficulty level
ScoreStrategy
3rd
Only one
Fallback strategy (Default { Correct = 1, Incorrect = 0, Unattempted = 0})
4th (lowest)
Configured for the entire app. Shared by all quizzes
I have a container class called EvaluationStrategy that holds these score strategies among other evaluation info:
partial class EvaluationStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
public decimal MaxScore { get; set; }
public decimal PassingScore { get; get; }
public IEnumerable<ScoreStrategy> ScoreStrategies { get; set; }
}
What I have tried:
I have added a method called GetStrategyByQuestion() to the same EvaluationStrategy class above(note it is declared as partial) that implements this fallback behavior and also a companion indexer that in turn calls this method. I have declared two HashSets of types DifficultyScoreStrategy and QuestionScoreStrategy and an Initialize() method instantiates them. All the score strategies are then switched by type and added to the appropriate HashSet, there can only be one ScoreStrategy per quiz, which will be stored in defaultStrategy:
partial class EvaluationStrategy
{
private ScoreStrategy FallbackStrategy = new() { Correct = 1, Incorrect = 0, Unattempted = 0 };
private ScoreStrategy defaultStrategy;
HashSet<DifficultyScoreStrategy> dStrategies;
HashSet<QuestionScoreStrategy> qStrategies;
public void Initialize()
{
qStrategies = new();
dStrategies = new();
// Group strategies by type
foreach (var strategy in strategies)
{
switch (strategy)
{
case QuestionScoreStrategy qs: qStrategies.Add(qs); break;
case DifficultyScoreStrategy ds: dStrategies.Add(ds); break;
case ScoreStrategy s: defaultStrategy = s; break;
}
}
}
public ScoreStrategy this[Question question] => GetStrategyByQuestion(question);
public ScoreStrategy GetStrategyByQuestion(Question question)
{
if (qStrategies is null || dStrategies is null)
{
Initialize();
}
// Check if question strategy exists
if (qStrategies.FirstOrDefault(str => str.Question.Id == question.Id) is not null and var qs)
{
return qs;
}
// Check if difficulty strategy exists
if (dStrategies.FirstOrDefault(str => str.Question.Difficulty == question.Difficulty) is not null and var ds)
{
return ds;
}
// Check if default strategy exists
if (defaultStrategy is not null)
{
return defaultStrategy;
}
// Fallback
return FallbackStrategy;
}
}
This method seems a bit clumsy and doesn't quite feel right to me. Using a partial class and adding to EvalutationStrategy doesn't seem right either. How do I implement this level-by-level fallback behavior? Is there a design pattern/principle I can use here? I know many things in the .NET framework fallback to default conventions if not configured. I need something similar. Or can someone simply recommend a cleaner and elegant solution with better maintainability?
NOTE/ADDITIONAL INFO: The ScoreStrategys and EvaluationStrategy for all quizzes are stored in a database managed by EF Core(.NET 5) with TPH mapping:
modelBuilder.Entity<ScoreStrategy>()
.ToTable("ScoreStrategy")
.HasDiscriminator<int>("StrategyType")
.HasValue<ScoreStrategy>(0)
.HasValue<DifficultyScoreStrategy>(1)
.HasValue<QuestionScoreStrategy>(2)
;
modelBuilder.Entity<EvaluationStrategy>().ToTable("EvaluationStrategy");
I have a single base DbSet<ScoreStrategy> ScoreStrategies and another DbSet<EvaluationStrategy> EvaluationStrategies. Since EvaluationStrategy is an EF Core class, I'm a bit skeptical about adding logic(GetStrategyByQuestion()) to it as well.
With Polly
There is a 3rd party library called Polly which defines a policy called Fallback.
With this approach you can easily define a fallback chain like this:
public ScoreStrategy GetStrategyByQuestionWithPolly(Question question)
{
Func<ScoreStrategy, bool> notFound = strategy => strategy is null;
var lastFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(FallbackStrategy);
var defaultFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(defaultStrategy);
var difficultyFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(() => GetApplicableDifficultyScoreStrategy(question));
var fallbackChain = Policy.Wrap(lastFallback, defaultFallback, difficultyFallback);
fallbackChain.Execute(() => GetApplicableQuestionScoreStrategy(question));
}
I've extracted the strategy selection logic for QuestionScoreStrategy and DifficultyScoreStrategy like this:
private ScoreStrategy GetApplicableQuestionScoreStrategy(Question question)
=> qStrategies.FirstOrDefault(str => str.Question.Id == question.Id);
private ScoreStrategy GetApplicableDifficultyScoreStrategy(Question question)
=> dStrategies.FirstOrDefault(str => str.Difficulty == question.Difficulty);
Pros
There is a single return statement
The policy declarations are separated from chaining
Each and every fallback can be triggered by different conditions
Primary selection logic is separated from the fallbacks
Cons
The code is really repetitive
You can't create a fallback chain by utilizing a fluent API
You need to use a 3rd party library
Without Polly
If you don't want to use a 3rd party library just to define and use a fallback chain you do something like this:
public ScoreStrategy GetStrategyBasedOnQuestion(Question question)
{
var fallbackChain = new List<Func<ScoreStrategy>>
{
() => GetApplicableQuestionScoreStrategy(question),
() => GetApplicableDifficultyScoreStrategy(question),
() => defaultStrategy,
() => FallbackStrategy
};
ScoreStrategy selectedStrategy = null;
foreach (var strategySelector in fallbackChain)
{
selectedStrategy = strategySelector();
if (selectedStrategy is not null)
break;
}
return selectedStrategy;
}
Pros
There is a single return statement
The fallback chain declaration and evaluation are separated
It is simple and concise
Cons
It is less flexible: each fallback selection is triggered by the same condition
Primary selection is not separated from fallbacks
You can sort the sequence of ScoringMethods by your priority.
First you sort by whether str is QuestionScoreStrategy and str.Question.Id == question.Id.
Then you sort by whether str is DifficultyScoreStrategy and str.Question.Difficulty == question.Difficulty.
(Note that since false comes before true, you'll have to invert the conditions)
Then you can just do FirstOrDefault() ?? defaultStrategy.
Example:
var defaultStrategy = new() { Correct = 1, Incorrect = 0, Unattempted = 0 };
var selectedStrategy = Strategies.OrderBy(str =>
!(str is QuestionScoreStrategy questionStrat && questionStrat.Question.Id == question.Id)
).ThenBy(str =>
!(str is DifficultyScoreStrategy difficultySrat && difficultySrat.Difficulty == question.Difficulty)
).FirstOrDefault() ?? defaultStrategy;
You can easily add more "levels" to this by adding more ThenBy clauses.
I imagine that all data (questions, strategies, quizes is stored in database). Then I would expect such ways of getting each strategy:
Question strategy
var questionStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.QuesionId == question.Id);
Difficulty strategy:
var difficultyStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.Difficulty == question.Difficulty);
Default strategy for quiz:
var quizStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.QuizId == question.QuizId)
Building on this and what you already provided, strategy is just three numbers: points for correct answer, points for incorrect and unattempted answer.
So this makes perfect candidate for abstract class, which would serve for base class for three entities - three types of strategy - those will be three tables, because each has different relations:
public abstract class ScoreStrategy
{
public double Correct { get; set; }
public double Incorrect { get; set; }
public double Unattempted { get; set; }
}
// Table with FK relation to Questions table
public class QuestionScoreStrategy : ScoreStrategy
{
public Question { get; set; }
public int QuestionId { get; set; }
}
// If you have table with difficulties, there should be FK relation to it.
// If you do not have table - it's worth consideration, you could then
// easily add more difficulties.
public class DifficultyStrategy : ScoreStrategy
{
public QuestionDifficulty Difficulty { get; set; }
}
// FK relation to Quizes table
public class QuizScoreStrategy : ScoreStrategy
{
public Quiz { get; set; }
public int QuizId { get; set; }
}
This way you end up with well grained tables that stores only relevant data.
Then, usage would become:
// Ideally, this method should be in some repoistory (look at repository design pattern) in data access layer
// and should leverage usage of async / await as well.
public ScoreStrategy GetScoreStrategy(Question question)
{
return dbContext.QuestionScoreStrategies.SingleOrDefault(qs => qs.QuestionId == question.Id)
?? dbContext.DifficultyStrategies.SingleOrDefault(ds => ds.Difficulty == question.Difficulty)
?? dbContext.QuizScoreStrategies.SingleOrDefault(qs => qs.QuizId == question.QuizId);
}
Then you could use this method in such way:
// This should be outside data access layer. Here you perform logic of getting question.
// This could be some ScoringManager class which should be singleton (one instance only).
// Then you could define fallback in private fields:
private readonly double FALLBACK_CORRECT_SCORE;
private readonly double FALLBACK_INCORRECT_SCORE;
private readonly double FALLBACK_UNATTEMPTED_SCORE;
// private constructor, as this should be singleton
private ScoringManager(double correctScore, double incorrectScore, double unattemptedScore)
=> (FALLBACK_CORRECT_SCORE, FALLBACK_INCORRECT_SCORE, FALLBACK_UNATTEMPTED_SCORE) =
(correctScore, incorrectScore, unattemptedScore);
public double CalcScoreForQuestion(Question question)
{
var scoreStrategy = GetScoreStrategy(question);
if (question answered correctly)
return scoreStrategy?.Correct ?? FALLBACK_CORRECT_SCORE;
if (question answered incorrectly)
return scoreStrategy?.Incorrect ?? FALLBACK_INCORRECT_SCORE;
if (question unattempted)
return scoreStrategy?.Unattempted ?? FALLBACK_UNATTEMPTED_SCORE;
}
NOTE
This is just the draft how I would organize things and most probably when writing code I would come up with improvements, but I think this is direction to go. For example ScoringManager could have ConfigureFallbackScore method, which would allow dynamically changing fallback scores (this would require making respective fields not readonly).
UPDATE
Define fallback strategy, in order to do that define enum:
public enum FallbackLevel
{
Difficulty,
Question,
Quiz,
}
Then scoring manager could have method to configure strategy (together with backing fields):
private FallbackLevel _highPrecedence;
private FallbackLevel _mediumPrecedence;
private FallbackLevel _lowPrecedence;
public void ConfigureFallbackStrategy(FallbackLevel highPrecedence, FallbackLevel mediumPrecedence, FallbackLevel lowPrecedence)
{
_highPrecedence = highPrecedence;
_mediumPrecedence = mediumPrecedence;
_lowPrecedence = lowPrecedence;
}
Then we would write getting strategy logic in manager:
public ScoreStrategy GetScoreStrategy(Question question)
{
var scoreStrategy = GetScoreStrategy(_highPrecedence, question)
?? GetScoreStrategy(_mediumPrecedence, question)
?? GetScoreStrategy(_lowPrecedence, question);
}
private ScoreStrategy GetScoreStrategy(FallbackLevel lvl, Question question) => lvl switch
{
FallbackLevel.Difficulty => dbContext.DifficultyStrategies.SingleOrDefault(ds => ds.Difficulty == question.Difficulty),
FallbackLevel.Question => dbContext.QuestionScoreStrategies.SingleOrDefault(qs => qs.QuestionId == question.Id),
FallbackLevel.Quiz => dbContext.QuizScoreStrategies.SingleOrDefault(qs => qs.QuizId == question.QuizId),
}
This way it is super easy to configure fallback strategy any way you want. Of course, there are some considerations still:
make sure that all fallback strategies are unique, so for example it is impossible to have high, medium and low startegy the same,
db context should be accessed only via repository pattern
add some more sanity checks (like nulls etc.)
I omitted those parts, as I focused on sheer functionality.
I want to create a structure to store data consumed from a Web Service with the followind specs:
Response:
Field 1 - InstructionType: Can be 1 (PreferredDay), 2 (SVP), 3 (Neighbour)
Field 2: Some variable data. Its type depends on Field 1. So if:
Field 1 == 1 then Field 2 type will be of DateTime (dd.MM.yyyy)
Field 1 == 2 then Field 2 type will be of type string.
Field 1 == 3 then Field 2 type will be of type string
So, I started up with the following enum:
public enum InstructionType
{
None = 0,
PreferredDay = 1,
ServicePoint = 2,
Neighbour = 3
}
And the generic class:
public abstract class Instruction<T>
{
public InstructionType Type { get; private set; }
public T Data { get; private set; }
public Instruction(InstructionType type, T data)
{
this.Type = type;
this.Data = data;
}
}
and concrete classes:
public class PreferredDayInstruction : Instruction<DateTime>
{
public PreferredDayInstruction(DateTime data)
: base (InstructionType.PreferredDay, data) {}
}
public class ServicePointInstruction: Instruction<string>
{
public ServicePointInstruction(string data)
: base (InstructionType.ServicePoint, data) {}
}
public class NeughbourInstruction: Instruction<string>
{
public NeughbourInstruction(string data)
: base (InstructionType.Neighbour, data) {}
}
When parsing web service's response created a public function:
public Instruction DeliveryInstruction() <---Compiler error here "Instruction"
{
if (resultFromWebservice.Field1 == 1)
return new PreferredDayInstruction((DateTime)Field2);
if (resultFromWebservice.Field1 == 2)
return new ServicePointInstruction(Field2);
if (resultFromWebservice.Field1 == 3)
return new NeighbourInstruction(Field2);
}
and here is the problem. Can't return objects of generic type.
Tried with with Interface, factories, and other stuff, but allways with the same problem. So, is there any way to archieve this? maybe it's not possible or maybe is so easy I can't see now. Thanks in advance.
UPDATE:
Compiler error on BOLD Instruction
Error 1 Using the generic type 'NAMESPACE.Instruction' requires '1' type arguments
I forgot..I'm using .NET 3.5
It looks like you may be starting off with an intent to use generics rather than using them because you've identified a need. Often (not always) when that gets difficult it's because it didn't actually fit what you were trying to do.
What seems odd in this case is that you have both a generic type and an enum to indicate the type. This is likely to cause you a few problems.
First it looks like you're trying to create a one-size-fits all class to model different types of behaviors. That will start off confusing and get more confusing. Think of most classes that are part of the .NET framework, and imagine what would happen if they had properties like Field1 and Field2, and you couldn't tell from looking at them what they were for. And in one method they're used for one thing, but in a another case they mean something else.
Also, if you're trying to put different types of instructions in one class, that suggests that maybe you're going to try passing them all to one method, and that method figures out what to do, and maybe calls other methods. (I'm guessing that because of the enum. Perhaps you're going to handle the input differently depending on which value it contains.) That one method will get really hard to maintain.
I'd recommend waiting on generics until you're sure you need them. And if you have different types of instructions you're likely better off writing a different class for each one with the properties it needs and names that describe them, and writing methods for each of them to do what they need to do. If you need lots of classes, make lots of them.
It's very easy to fall into the trap of trying to solve problems that don't exist, like how do I write one class that covers a bunch of different needs. The answer usually that you don't need to. You'll get better results from writing more classes that each do fewer things.
Believe me that I tried to do my best to explain what was my problem and what I needed in order to solve it. In a nutshell, the question was quite simple. Is this possible or not? So, is there a way to return a common type for these 3 classes? Answer is no, as they don't share any root. They all derive from Instruction, but aren't compatible each other. That's what I learned from this experience.
As another example, lets take another .NET framework's generic type.
public class ListOfString : List<string> { }
public class ListOfInt : List<int> { }
public class ListOfDecimal : List<decimal> { }
And, in another place of the application, get a method who returns one of this List based on some logic:
public class Logic
{
public List<> GetList(Type t) <----This can't be done
{
if (t == typeof(string))
return new ListOfString();
if (t == typeof(int))
return new ListOfInt();
if (t == typeof(decimal))
return new ListOfDecimal();
else return null;
}
}
Please, keep in mind that this is just a stupid sample just to show what's the point of this post.
By the way, in the case of List the following can be done, because there is a non generic different version of IList:
public IList GetList(Type t)
{
....
}
But I can't think of a way to do this in my particular case.
Anyway, I finally followed another approach. I reallized that what I really wanted is to ensure Data property is valid. If it it's supposed to be a date there, ensure date is valid. Is it a string, ensure it has the right length or whatever rule it must follow.
So this is the final solution:
The enum:
public enum InstructionType
{
None = 0,
PreferredDay = 1,
ServicePoint = 2,
Neighbour = 3
}
The base class:
public abstract class Instruction
{
public InstructionType Type { get; private set; }
public string Data { get; private set; } <---Type String
public Instruction(InstructionType type, string data)
{
this.Type = type;
this.Data = IsValid(data) ? data : string.Empty;
}
public abstract bool IsValid(string data); <--the rule.
}
The concrete classes:
public class PreferredDayInstruction : Instruction
{
public PreferredDayInstruction(string date)
: base(InstructionType.PreferredDay, date) { }
public override bool IsValid(string data)
{
string[] formats = {"dd.MM.yyyy", "d.MM.yyyy",
"dd.MM.yy", "d.MM.yy"};
try
{
data = data.Replace('/', '.').Replace('-', '.');
var dateparts = data.Split('.');
DateTime date = new DateTime(Convert.ToInt32(dateparts[2]),
Convert.ToInt32(dateparts[1]),
Convert.ToInt32(dateparts[0]));
//DateTime.ParseExact(data, formats, null, System.Globalization.DateTimeStyles.AssumeLocal);
return true;
}
catch (Exception)
{
return false;
}
}
}
public class ServicePointInstruction : Instruction
{
public ServicePointInstruction(string data)
: base (InstructionType.ServicePoint, data) { }
public override bool IsValid(string data)
{
return ServicePointBarcodeValidator.Validate(data);
}
}
public class NeighbourInstruction : Instruction
{
public NeighbourInstruction(string data) :
base(InstructionType.Neighbour, data) { }
public override bool IsValid(string data)
{
return data.Length <= 70;
}
}
A factory class, who's responsability is to create and return the correct object based on the enum:
public static class DeliveryInstructionFactory
{
public static Instruction Create(int type, string data)
{
return Create((InstructionType)type, data);
}
public static Instruction Create(InstructionType type, string data)
{
switch (type)
{
case InstructionType.PreferredDay:
return new PreferredDayInstruction(data);
case InstructionType.ServicePoint:
return new ServicePointInstruction(data);
case InstructionType.Neighbour:
return new NeighbourInstruction(data);
default:
return null;
}
}
}
And finally, as now all of they share the same root, object can be created on webservice's response parser:
public Instruction DeliveryInstruction()
{
try
{
int instructionCode = int.Parse(observation.Substring(173,2));
string instructionData = observation.Substring(175, 10);
return DeliveryInstructionFactory.Create(instructionCode, instructionData); }
catch (Exception ex)
{
Log.Error("[ValidationBarcodeResponse] DeliveryInstructions aren't in the correct format", ex);
return null;
}
}
Hope this now fits on a Minimal, Complete, and Verifiable example
I'd like to compare two custom class objects of the same type. The custom class being compared has a List property which is filled with items of another custom type. Is this possible by inheriting IEquatable?
I couldn't figure out how to make this work by modifying MSDN's code to compare class objects containing List properties of a custom type.
I did successfully derive from the EqualityComparer class to make a separate comparison class (code below), but I'd like to implement the comparison ability in the actual classes being compared. Here's what I have so far:
EDIT: This doesn't work after all. My apologies - I've been working on this awhile and I may have pasted incorrect example code. I'm working on trying to find my working solution...
class Program
{
static void Main(string[] args)
{
// Test the ContractComparer.
Contract a = new Contract("Contract X", new List<Commission>() { new Commission(1), new Commission(2), new Commission(3) });
Contract b = new Contract("Contract X", new List<Commission>() { new Commission(1), new Commission(2), new Commission(3) });
ContractComparer comparer = new ContractComparer();
Console.WriteLine(comparer.Equals(a, b));
// Output returns True. I can't get this to return
// True when I inherit IEquatable in my custom classes
// if I include the list property ("Commissions") in my
// comparison.
Console.ReadLine();
}
}
public class Contract
{
public string Name { get; set; }
public List<Commission> Commissions { get; set; }
public Contract(string name, List<Commission> commissions)
{
this.Name = name;
this.Commissions = commissions;
}
}
public class Commission
{
public int ID;
public Commission(int id)
{
this.ID = id;
}
}
public class ContractComparer : IEqualityComparer<Contract>
{
public bool Equals(Contract a, Contract b)
{
//Check whether the objects are the same object.
if (Object.ReferenceEquals(a, b)) return true;
//Check whether the contracts' properties are equal.
return a != null && b != null && a.Name.Equals(b.Name) && a.Commissions.Equals(b.Commissions);
}
public int GetHashCode(Contract obj)
{
int hashName = obj.Name.GetHashCode();
int hashCommissions = obj.Commissions.GetHashCode();
return hashName ^ hashCommissions;
}
}
You have to implement some kind of comparer for Commission, e.g. by implementing Commission : IEquatable<Commission>, then use it:
... && a.Commissions.SequenceEqual(b.Commissions)
I'm using EF4.3 so I'm referring to entities, however it could apply to any class containing properties.
I'm trying to figure out if its possible to compare 2 entities. Each entity has properties that are assigned values for clarity let say the entity is 'Customer'.
public partial class Customer
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
...
...
}
The customer visits my website and types in some details 'TypedCustomer'. I check this against the database and if some of the data matches, I return a record from the database 'StoredCustomer'.
So at this point I've identified that its the same customer returning but I wan't to valid the rest of the data. I could check each property one by one, but there are a fair few to check. Is it possible to make this comparison at a higher level which takes into account the current values of each?
if(TypedCustomer == StoredCustomer)
{
.... do something
}
If you're storing these things in the database, it is logical to assume you'd also have a primary key called something like Id.
public partial class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
...
...
}
Then all you do is:
if(TypedCustomer.Id == StoredCustomer.Id)
{
}
UPDATE:
In my project, I have a comparer for these circumstances:
public sealed class POCOComparer<TPOCO> : IEqualityComparer<TPOCO> where TPOCO : class
{
public bool Equals(TPOCO poco1, TPOCO poco2)
{
if (poco1 != null && poco2 != null)
{
bool areSame = true;
foreach(var property in typeof(TPOCO).GetPublicProperties())
{
object v1 = property.GetValue(poco1, null);
object v2 = property.GetValue(poco2, null);
if (!object.Equals(v1, v2))
{
areSame = false;
break;
}
});
return areSame;
}
return poco1 == poco2;
} // eo Equals
public int GetHashCode(TPOCO poco)
{
int hash = 0;
foreach(var property in typeof(TPOCO).GetPublicProperties())
{
object val = property.GetValue(poco, null);
hash += (val == null ? 0 : val.GetHashCode());
});
return hash;
} // eo GetHashCode
} // eo class POCOComparer
Uses an extension method:
public static partial class TypeExtensionMethods
{
public static PropertyInfo[] GetPublicProperties(this Type self)
{
self.ThrowIfDefault("self");
return self.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where((property) => property.GetIndexParameters().Length == 0 && property.CanRead && property.CanWrite).ToArray();
} // eo GetPublicProperties
} // eo class TypeExtensionMethods
Most simple seems to use reflexion : get the properties and/or fields you want to compare, and loop through them to compare your two objects.
This will be done with getType(Customer).getProperties and getType(Customer).getFields, then using getValue on each field/property and comparing.
You might want to add custom informations to your fields/properties to define the ones that needs
comparing. This could be done by defining a AttributeUsageAttribute, that would inherit from FlagsAttribute for instance. You'll then have to retrieve and handle those attributes in your isEqualTo method.
I don't think there's much of a purpose to checking the entire object in this scenario - they'd have to type every property in perfectly exactly as they did before, and a simple "do they match" doesn't really tell you a lot. But assuming that's what you want, I can see a few ways of doing this:
1) Just bite the bullet and compare each field. You can do this by overriding the bool Equals method, or IEquatable<T>.Equals, or just with a custom method.
2) Reflection, looping through the properties - simple if your properties are simple data fields, but more complex if you've got complex types to worry about.
foreach (var prop in typeof(Customer).GetProperties()) {
// needs better property and value validation
bool propertyMatches = prop.GetValue(cust1, null)
.Equals(prop.GetValue(cust2, null));
}
3) Serialization - serialize both objects to XML or JSON, and compare the strings.
// JSON.NET
string s1 = JsonConvert.SerializeObject(cust1);
string s2 = JsonConvert.SerializeObject(cust2);
bool match = s1 == s2;
Currently I have a bunch of if else statements to set CategoryId's based on how many items are in each collection.
For example,
public class TeamWork
{
public string EmployeeName { get; set; }
public int CategoryId { get; set; }
}
public class BLL
{
public void SetCategoryId(ICollection<TeamWork> Converted, ICollection<TeamWork> Sourced)
{
if (Converted.Count == 1 && Sourced.Count == 1)
{
if (String.Compare(Sourced.First().EmployeeName, Converted.First().EmployeeName) == 0)
{
// set category id to 1
Converted.First().CategoryId = 1;
Sourced.First().CategoryId = 1;
}
else
{
// set category id to something
}
}
else if (Sourced.Rows.Count == 1 && Converted.Rows.Count > 1)
{
// set category id to something
}
// more if else statements...
}
}
I think there's a better way to do this perhaps by applying some design pattern. Any suggestions? Thanks!
Chain of responsibility is the way to go.
So this object is passed to a series of command objects until one is able to act upon and set the status.
A Strategy pattern comes to mind. Try to break these rules down into a series of "if this condition is true, then the category ID is this". Make each one of these a method, then add those methods as delegates to a List<Func<ICollection<TeamWork>, ICollection<TeamWork>, bool>> or a comparable indexed collection. Then, your SetCategoryId() code looks like this:
public void SetCategoryId(ICollection<TeamWork> Converted, ICollection<TeamWork> Sourced)
{
foreach(var categoryRule in CategoryRules)
{
var category = test(Converted, Sourced);
if(category != 0)
{
Converted.First().CategoryId = Sourced.First().CategoryId = category;
break;
}
}
}
The above code would never have to change regardless of how many rules you added or removed. However, with the if - else if structure you have, your series of rules will likely be order-dependent, so be careful when setting up the rules in the list.