I have a IHasTag interface, a TaggableItem class that implements IHasTag and a TaggingManager class which I want to make it the only one responsible for setting or clearing the Tags but I've been struggling all day trying to implement but with no luck.
How can I make this possible?
public class TaggableItem : TaggingManager.IHasTag
{
public string Tag { get; } // read only
}
public class TaggingManager
{
public interface IHasTag
{
string Tag { get; }
}
private List<IHasTag> _taggedItems = new();
public void TagItem(IHasTag item, string tag)
{
item.Tag = tag; // not working
_taggedItems.Add(item);
}
public void ClearAllTags()
{
foreach (var item in _taggedItems)
{
item.Tag = "": // not working
}
_taggedItems.Clear();
}
}
EDIT
I followed Thomas' suggestion and this is what I end up doing. I know it's not perfect, though. Thank you all for your advices.
public interface ITaggable
{
string? Tag { get; }
}
public interface ISelectable
{
bool IsSelected { get; }
}
public interface IItem : ITaggable, ISelectable
{
}
public class Item : IItem
{
protected Item() { }
public bool IsSelected { get; set; }
public string Tag { get; set; } = string.Empty;
// 'Item' will be created here returning IItem.
// So, unless you 'cast' it, you can't set 'Tag' or 'IsSelected'.
public static IItem CreateItem() => new Item();
}
public class SelectionManager
{
protected List<object> _items = new();
public void Select(ISelectable item)
{
if (item is Item selectable)
{
selectable.IsSelected = true;
_items.Add(item);
}
}
public void Unselect(ISelectable item)
{
if (item is Item selectable)
{
selectable.IsSelected = false;
_items.Remove(item);
}
}
}
public class TaggingManager
{
private List<object> _items = new();
public void Tag(ITaggable item, string tag)
{
if (item is Item taggable)
{
taggable.Tag = tag;
_items.Add(item);
}
}
public void Untag(ITaggable item)
{
if (item is Item taggable)
{
taggable.Tag = string.Empty;
_items.Remove(item);
}
}
}
My suggestion would be to have two interfaces for two purposes: reading and writing.
public interface IHasTag // interface for reading. Maybe IReadTag
{
string Tag { get; }
}
public interface ITagChange // interface for writing. Maybe IWriteTag
{
string Tag { set; }
}
public class TaggableItem : IHasTag, ITagChange // implement both
{
public string Tag { get; set; }
}
// Tagging manager gets write access (ITagChange)
public class TaggingManager
{
private List<ITagChange> _taggedItems = new ();
public void TagItem(ITagChange item, string tag)
{
item.Tag = tag;
_taggedItems.Add(item);
}
public void ClearAllTags()
{
foreach (var item in _taggedItems)
{
item.Tag = "";
}
_taggedItems.Clear();
}
}
// Everyone else has read access only (IHasTag)
class SomeoneElse
{
private List<IHasTag> _taggedItems = new ();
public void DoSomething(IHasTag item)
{
_taggedItems.Add(item);
var tag = item.Tag; // do something with the tag
}
}
class Instantiation
{
public void Main()
{
TaggableItem x = new TaggableItem();
TaggingManager m = new TaggingManager();
m.TagItem(x, "name");
SomeoneElse s = new SomeoneElse();
s.DoSomething(x);
}
}
One possible option is to move the "getter" also to the TagManager. Then the manager is responsible for the tags. The object itself does not even need to know about the tags.
You still can restrict this by exchanging object with an interface.
public class TagManager
{
private Dictionary<object, string> _tagedItems = new Dictionary<object, string>();
public bool HasTag(object item)
{
return _tagedItems.ContainsKey(item);
}
public string GetTag(object item)
{
return _tagedItems[item];
}
public void SetTag(object item, string tag)
{
if(!HasTag(item))
{
_tagedItems.Add(item, tag);
}
else
{
_tagedItems[item] = tag;
}
}
}
I don't think that this is really a answer to the OP, but one possible solution for the underlying problem.
Related
I have 3 interfaces.
public interface IItem
{
string Name { get; set; }
}
public interface IEquipable : IItem
{
void Equip();
}
public interface IConsumable : IItem
{
void Use();
}
IEquipable is implemented by the classes Helmet and Bow, and IConsumable is implemented by classes Potion and Food.
Then, I have a class with a property which contains a List of IItem, and proceed to add a few items of both IEquipable and IConsumable after instantiating it.
public class Character
{
public List<IItem> Items { get; private set; }
public Character()
{
this.Items = new List<IItem>();
}
public void AddItem(IItem item)
{
this.Items.Add(item);
}
}
Program.cs
...
Character char = new Character();
char.AddItem(new Potion());
char.AddItem(new Food());
char.AddItem(new Helmet());
char.AddItem(new Bow());
...
Is there a way I can get a List of all IEquipable members from the List of IItems, each AS IEquipable?
I want to do something like
...
List<IEquipable> equipmentList = //do something to char.Items and get all items of type IEquipable.
IEquipment equipment = equipmentList.First(...)
equipment.Equip();
...
I've tried using List<IEquipable> equipmentList = char.Items.OfType<IEquipable>().ToList() but the resulting list ends up empty.
I implemented (and fixed minor typos in) your code like this:
void Main()
{
Character character = new Character();
character.AddItem(new Potion());
character.AddItem(new Food());
character.AddItem(new Helmet());
character.AddItem(new Bow());
List<IEquipable> equipmentList = character.Items.OfType<IEquipable>().ToList();
}
public class Potion : IConsumable
{
public string Name { get; set; }
public void Use()
{
throw new NotImplementedException();
}
}
public class Food : IConsumable
{
public string Name { get; set; }
public void Use()
{
throw new NotImplementedException();
}
}
public class Helmet : IEquipable
{
public string Name { get; set; }
public void Equip()
{
throw new NotImplementedException();
}
}
public class Bow : IEquipable
{
public string Name { get; set; }
public void Equip()
{
throw new NotImplementedException();
}
}
public interface IItem
{
string Name { get; set; }
}
public interface IEquipable : IItem
{
void Equip();
}
public interface IConsumable : IItem
{
void Use();
}
public class Character
{
public List<IItem> Items { get; private set; }
public Character()
{
this.Items = new List<IItem>();
}
public void AddItem(IItem item)
{
this.Items.Add(item);
}
}
Your exact code (albeit char renamed to character) works perfectly fine. The equipmentList ends up with two elements. The issue you're seeing, i.e. "the resulting list ends up empty", is not reproducible with the code you've posted.
You can use the OfType method
Filters the elements of an IEnumerable based on a specified type.
Signature
public static IEnumerable<TResult> OfType<TResult> (this IEnumerable source)
Usage
var equipable = Character.Items.OfType<IEquipable>();
Or encapsulate it as a method in the instance or an extension method if you like
So it does work like I wanted. My actual code just had another issue and I'm a dummy for not actually posting that. So here it is, for future reference.
using System.Collections.Generic;
using RolePlayGame.Library.Items.Backstage;
using System.Linq;
using System.Text;
using System;
namespace RolePlayGame.Library.Characters.Backstage
{
public class Inventory
{
public List<IItem> StoredItems { get; private set; }
public List<EquippedItem> Gear { get; private set; }
public Inventory()
{
this.StoredItems = new List<IItem>();
this.Gear = new List<EquippedItem>();
}
public bool HasItem(string name)
{
return this.StoredItems.Exists(item => item.Name == name);
}
public bool HasItem(IItem item)
{
return this.StoredItems.Contains(item);
}
public void RemoveItem(string name)
{
int firstIndex = this.StoredItems.FindIndex(item => item.Name == name);
if (firstIndex != -1)
{
this.StoredItems.RemoveAt(firstIndex);
}
}
public void RemoveItem(IItem item)
{
int firstIndex = this.StoredItems.IndexOf(item);
if (firstIndex != -1)
{
this.StoredItems.RemoveAt(firstIndex);
}
}
public void AddItem(IItem item, int quantity)
{
for (int i = 0; i < quantity; i++)
{
this.StoredItems.Add(item);
}
}
public void AddItem(IItem item)
{
this.StoredItems.Add(item);
}
public bool CheckEquipmentSlot(EquipmentSlot slot)
{
return this.Gear.Exists(item => item.UsedSlots.Contains(slot));
}
public bool HasEquipment(IEquipment equipment)
{
return this.Gear.Exists(item => item.Item == equipment);
}
public void AddEquipment(IEquipment equipment)
{
IEquipment alreadyEquipped;
foreach (EquipmentSlot slot in equipment.SlotsUsed)
{
if (this.Gear.Exists(item => item.UsedSlots.Contains(slot)))
{
alreadyEquipped = this.Gear.Find(item => item.UsedSlots.Contains(slot)).Item;
this.RemoveEquipment(slot);
this.StoredItems.Add(alreadyEquipped);
}
}
EquippedItem newEquipment = new EquippedItem(equipment);
this.Gear.Add(newEquipment);
}
public void RemoveEquipment(EquipmentSlot slot)
{
this.Gear.RemoveAll(equipment => equipment.UsedSlots.Contains(slot));
}
public int GetAttributeBonusTotal(AttributeType attribute)
{
int bonusTotal = 0;
foreach (IEquipment item in this.StoredItems.OfType<IEquipment>().ToList())
{
bonusTotal += item.GetAttributeBonus(attribute);
}
return bonusTotal;
}
public int GetCarryWeight()
{
int totalWeight = 0;
foreach (IItem item in StoredItems)
{
totalWeight += item.Weight;
}
return totalWeight;
}
public string GearToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(" Equipped Gear:");
foreach (EquippedItem equipment in this.Gear)
{
builder.Append($"\n {equipment.Item.Name}");
}
return builder.ToString();
}
public string ItemsToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(" Inventory:");
foreach (IItem item in this.StoredItems.Distinct())
{
builder.Append($"\n {item.Name} x {this.StoredItems.FindAll(value => value == item).Count()}");
}
return builder.ToString();
}
public int GetDefenseRateAgainstTypeTotal(DamageType againstType)
{
int rate = 0;
List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList();
foreach (IOutfit item in outfits)
{
rate += item.GetDefenseRateAgainstType(againstType);
}
return rate;
}
}
}
One of the last lines has the problem (now fixed). List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList(); used to be List<IOutfit> outfits = this.Gear.OfType<IOutfit>().ToList();. But Gear is of type List<EquippedItem>, and EquippedItem is not an implementation of IItem.
Here is EquippedItem.cs
using RolePlayGame.Library.Items.Backstage;
using System.Collections.Generic;
namespace RolePlayGame.Library
{
public class EquippedItem
{
public List<EquipmentSlot> UsedSlots { get; set; }
public IEquipment Item { get; set; }
public EquippedItem(IEquipment equipment)
{
this.Item = equipment;
this.UsedSlots = equipment.SlotsUsed;
}
}
}
I needed to select the Item property from the items inside Gear as another list before doing the type filtering with .OfType<IOutfit>(). That's where .Select(value => value.Item) enters the stage.
So that's that. I'll learn to post actual code for future questions.
I need to collect some classes and provide it by request to some parts of program. I have following code:
public interface ISameClass
{
int Value { get; set; }
void DoStuff();
}
public class SameClass : ISameClass
{
int Value { get; set; }
void DoStuff()
{
//Do something
}
}
public class SameClassProvider
{
private readonly Dictionary<string, ISameClass> _sameClasses;
public SameClassProvider(string parentDir)
{
_sameClasses = new Dictionary<string, ISameClass>
{
{ "Type1", new SameClass() },
{ "Type2", new SameClass() },
{ "Type3", new SameClass() }
};
}
public bool AddClass(string type, ISameClass class)
{
if (_sameClasses.ContainsKey(type) || class == null)
{
return false;
}
_nodes.Add(type, class);
return true;
}
public ISameClass GetClass(string type)
{
if (_sameClasses.TryGetValue(type, out var someClass))
{
return someClass;
}
}
}
Is using classes like SameClassProvider is good practice? Or i can refactor this something or replace with correct pattern? Tnx
I am trying to implement a high-performance game inventory system. I have This abstract base class to store different type of items in Inventory, for example, Coin, Flashlight, Knife etc..
public abstract class ObtainableItem
{
public string Name { get; private set; }
public ObtainableItem(string name)
{
Name = name;
}
}
For example, I have a DoorKey which opens a door. DoorKey has a property KeyCode which will be used for opening a door.
public class DoorKey : ObtainableItem
{
public int KeyCode { get; private set; }
public DoorKey() : base("key")
{
KeyCode = 1234;
}
}
All ObtainableItem are stored in Inventory
public class Inventory
{
const int slotCount = 2;
ObtainableItem[] slots = new ObtainableItem[slotCount];
public Inventory()
{
slots[0] = new DoorKey();
}
}
Now imagine user drags DoorKey from his Inventory on a Door and triggers Open method
public class Door
{
public void Open(ObtainableItem key)
{
if (key is DoorKey)
{
DoorKey doorKey = (DoorKey)key;
if (doorKey.KeyCode == 1234)
{
// Open door
}
}
else
{
// "can't use this item on a door"
}
}
}
How to avoid cast from ObtainableItem to a DoorKey? I have read that using casting is bad practice and it points at a bad code oop design. Ideally, a Door class should look like this. Is there any pattern I should for my inventory system?
public class Door
{
public void Open(DoorKey key)
{
if (key.KeyCode == 1234)
{
// Open door
}
}
}
There are always exceptions that can be made for ease of implementation and readability. What you describe is common, if not typical.
An alternative would be to have the "control" logic in the class that calls Door.Open. This could be easily achieved with a touch of reflection:
public abstract class ObtainableItem
{
public string Name { get; private set; }
public ObtainableItem(string name)
{
Name = name;
}
}
public abstract class WorldItem
{
}
public interface IActsOn<in TWorldItem>
where TWorldItem : WorldItem
{
void ApplyTo(TWorldItem worldItem);
}
public class World
{
// If profiling shows that this is a performance issue, a cache keyed by tWorldItem, tInvItem
// should fix it. No expiry or invalidation should be needed.
private Action<ObtainableItem, WorldItem> GetApplyTo(Type tWorldItem, Type tInvItem)
{
var tActOn = typeof(IActsOn<>).MakeGenericType(tWorldItem);
if (!tActOn.IsAssignableFrom(tInvItem))
{
return null;
}
var methodInfo = tActOn.GetMethod(nameof(IActsOn<WorldItem>.ApplyTo));
return new Action<ObtainableItem, WorldItem>((invItem, worldItem) =>
{
methodInfo.Invoke(invItem, new object[] { worldItem });
});
}
public bool IsDropTarget(WorldItem worldItem, ObtainableItem item)
=> GetApplyTo(worldItem.GetType(), item.GetType()) != null;
public void ActOn(WorldItem worldItem, ObtainableItem item)
{
var actOn = GetApplyTo(worldItem.GetType(), item.GetType());
if (actOn == null)
{
throw new InvalidOperationException();
}
actOn(item, worldItem);
}
}
While this slightly complicates the implementation of World, it simplifies the implementation of various objects:
class Door : WorldItem
{
public void Unlock(string bitting)
{
if (bitting == "1234")
{
Console.WriteLine("Door Opened");
}
else
{
Console.WriteLine("Door could not unlock");
}
}
}
class DoorKey : ObtainableItem, IActsOn<Door>
{
private readonly string Bitting;
public DoorKey(string bitting)
: base("Key")
{
this.Bitting = bitting;
}
public void ApplyTo(Door worldItem)
{
worldItem.Unlock(this.Bitting);
}
}
class RubberChicken : ObtainableItem
{
public RubberChicken()
: base("Rubber chicken")
{
}
}
Example usage:
class Program
{
static void Main(string[] args)
{
var key1 = new DoorKey("1234");
var key2 = new DoorKey("4321");
var rubberChicken = new RubberChicken();
var door = new Door();
var world = new World();
Debug.Assert(!world.IsDropTarget(door, rubberChicken));
Debug.Assert(world.IsDropTarget(door, key1));
world.ActOn(door, key2);
world.ActOn(door, key1);
Console.ReadLine();
}
}
I have two type of nodes, one is MyLinkNode it's used as base, another one is GraphNode which inheritance MyLinkNode.
I try to create MyQueue with MyLinkNode. Everything is OK until I try to add GraphNode to MyQueue. I can't use MyQueue with GraphNode since it's bigger.
An alternative way is create another queue for GraphNode, but that means I would need to create lots of class if I have more type of nodes.
Is there any suggestion?
public class MyQueue<T> where T : MyLinkNode<T>
{
private T Head;
private T Last;
public MyQueue(){ ... }
public void Enqueue(T item)
{
item.Prev = Last;
Last.Next = item;
Last = item;
}
}
public class MyGraphQueue
{
//everything is the same with MyQueue besides the Node Type
//I don't want to create like this.
private GraphNode Head;
private GraphNode Last;
public MyGraphQueue(){ ... }
public void Enqueue(GraphNode item)
{
item.Prev = Last;
Last.Next = item;
Last = item;
}
}
public class MyLinkNode<T>
{
public T data { get; set; }
public MyLinkNode<T> Next { get; set; }
public MyLinkNode<T> Prev { get; set; }
}
public class GraphNode<T> : MyLinkNode<T>
{
public GraphNode()
{
this.adjacencyNodes = new List<GraphNode<T>>();
this.isVisited = false;
}
public List<GraphNode<T>> adjacencyNodes;
public bool isVisited { get; set; }
}
public void BFS<T>(GraphNode<T> v)
{
MyQueue<GraphNode<T>> queue = new MyQueue<GraphNode<T>>(); // error, can't implicit convert GraphNode to MyLinkNode<T>
MyGraphQueue queue = new MyGraphQueue(); //It's how I do now.
}
This is a standard Generics inheritance problem. You need to separate what the Queue needs from the generic type. Just add another base class for the queue constraint.
This will keep the queue to have the guarantee of all items having type T and not require extra types or multiple class definitions for all of the concrete types. Eric Lippert has a good article here on why this limitation was required in the generics system.
public class CallingClass
{
public void BFS(GraphNode v)
{
MyQueue<GraphNode> queue = new MyQueue<GraphNode>(); // error, can't implicit convert GraphNode to MyLinkNode<T>
// MyGraphQueue queue = new MyGraphQueue(); //It's how I do now.
}
}
public class QueueItem
{
public QueueItem Next { get; set; }
public QueueItem Prev { get; set; }
}
public class MyQueue<T> where T : QueueItem
{
private T Head;
private T Last;
public MyQueue() { }
public void Enqueue(T item)
{
item.Prev = Last;
Last.Next = item;
Last = item;
}
}
public class MyLinkNode<T>: QueueItem
{
public T data { get; set; }
}
public class GraphNode : MyLinkNode<string>
{
public GraphNode()
{
this.adjacencyNodes = new List<GraphNode>();
this.isVisited = false;
}
public List<GraphNode> adjacencyNodes;
public bool isVisited { get; set; }
}
MyQueue<T> where T : MyLinkNode<T> cannot accept a MyLinkNode<string>
Beacuse here T is string. but, obviously string doesn't inherit from MyLinkNode
I think the solution is simpler than you imagine.
Just set the type (T) of the value in the queue, and inside use MyLinkNode<T>:
public class MyQueue<T>
{
private MyLinkNode<T> Head;
private MyLinkNode<T> Last;
public void Enqueue(MyLinkNode<T> item)
{
item.Prev = Last;
Last.Next = item;
Last = item;
}
}
public void BFS(GraphNode v)
{
MyQueue<string> queue = new MyQueue<string>(); // no error anymore
queue.Enqueue(v);
}
That's not surprising. You need
public class MyQueue<T, S> where T : MyLinkNode<S>
{
private T Head;
private T Last;
public MyQueue() { }
public void Enqueue(T item)
{
item.Prev = Last;
Last.Next = item;
Last = item;
}
}
public void BFS(GraphNode v)
{
MyQueue<GraphNode, string> queue = new MyQueue<GraphNode, string>();
}
The problem is coming from your first line.
Use:
public class MyQueue<T> where T : MyLinkNode<string> { }
Instead of:
public class MyQueue<T> where T : MyLinkNode<T> { }
and it will work fine.
I've been struggling with a piece of C# code and although I have found a solution to the problem, it is by no means ideal (see DoSomething_WorksButNotIdeal() below).
What I would like to do is instead of having the if, else statement (which is potentially massive depending on what types I want to support) just have a generic cast, but I can't get it to work. I've tried to demonstrate this in the DoSomething_HelpMe() method.
Is there anyway of achieving this? Any help is greatly appreciated.
public interface ITag
{
string TagName { get; }
Type Type { get; }
}
public interface ITag<T> : ITag
{
T InMemValue { get; set; }
T OnDiscValue { get; set; }
}
public class Tag<T> : ITag<T>
{
public Tag(string tagName)
{
TagName = tagName;
}
public string TagName { get; private set; }
public T InMemValue { get; set; }
public T OnDiscValue { get; set; }
public Type Type{ get{ return typeof(T);} }
}
public class MusicTrack
{
public MusicTrack()
{
TrackTitle = new Tag<string>("TrackTitle");
TrackNumber = new Tag<int>("TrackNumber");
Tags = new Dictionary<string, ITag>();
Tags.Add(TrackTitle.TagName, TrackTitle);
Tags.Add(TrackNumber.TagName, TrackNumber);
}
public IDictionary<string,ITag> Tags;
public ITag<string> TrackTitle { get; set; }
public ITag<int> TrackNumber { get; set; }
}
public static class Main
{
public static void DoSomething_WorksButNotIdeal()
{
MusicTrack track1 = new MusicTrack();
MusicTrack track2 = new MusicTrack();
// Set some values on the tracks
foreach (ITag tag in track1.Tags.Values)
{
Type type = tag.Type;
if (type == typeof(string))
{
((ITag<string>) tag).InMemValue = ((ITag<string>)track2.Tags[tag.TagName]).OnDiscValue;
}
else if (type == typeof(int))
{
((ITag<int>)tag).InMemValue = ((ITag<int>)track2.Tags[tag.TagName]).OnDiscValue;
}
else if (type == typeof(bool))
{
((ITag<bool>)tag).InMemValue = ((ITag<bool>)track2.Tags[tag.TagName]).OnDiscValue;
}
// etc etc
else
{
throw new Exception("Unsupported type.");
}
}
}
public static void DoSomething_HelpMe()
{
MusicTrack track1 = new MusicTrack();
MusicTrack track2 = new MusicTrack();
// Set some values on the tracks
foreach (ITag tag in track1.Tags.Values)
{
Type type = tag.Type;
// THIS OBVIOUSLY DOESN'T WORK BUT I'M JUST TRYING TO DEMONSTRATE WHAT
// I'D IDEALLY LIKE TO ACHIEVE
((ITag<typeof(type)>)tag).InMemValue = ((ITag<typeof(type)>)track2.Tags[tag.TagName]).OnDiscValue;
}
}
}
Any reason that you can't have:
public interface ITag
{
string TagName { get; }
Type Type { get; }
object InMemValue { get; set; }
object OnDiscValue { get; set; }
}
and use ITag<T> to make it more specific?
public interface ITag<T> : ITag
{
new T InMemValue { get; set; }
new T OnDiscValue { get; set; }
}
Then your method can just use ITag. You'd need something like (int Tag<T>):
object ITag.InMemValue
{
get { return InMemValue; }
set { InMemValue = (T)value; }
}
object ITag.OnDiscValue
{
get { return OnDiscValue; }
set { OnDiscValue = (T)value; }
}
(edit)
Another option would be a method on the non-generic ITag:
void CopyValueFrom(ITag tag);
(maybe a bit more specific about what it copies to/from)
Your concrete implementation (Tag<T>) would have to assume that the ITag is actually an ITag<T> and cast:
public void CopyFromTag(ITag tag) {
ITag<T> from = tag as ITag<T>;
if(from==null) throw new ArgumentException("tag");
this.TheFirstProperty = from.TheSecondProperty;
}
The simplest way to solve it is to resolve the type where you have the information, namely inside the Tag<T> implementation, so add the following to your existing types (only showing the additions!)
public interface ITag
{
void CopyFrom(bool sourceIsMem, ITag sourceTag, bool targetIsMem);
}
public class Tag<T> : ITag<T>
{
public void CopyFrom(bool sourceIsMem, ITag sourceTag, bool targetIsMem)
{
ITag<T> castSource = sourceTag as ITag<T>;
if (castSource == null)
throw new ArgumentException(
"Source tag is of an incompatible type", "sourceTag");
if (targetIsMem)
InMemValue = sourceIsMem ?
castSource.InMemValue : castSource.OnDiscValue;
else
OnDiscValue = sourceIsMem ?
castSource.InMemValue : castSource.OnDiscValue;
}
}
Note that you really should use enum types for the sourceIsMem and targetIsMem instead, because a bool is really ugly and hard to read in the invocation as the following fragment will show.
This is how you would make your routine work now:
public static void DoSomething_HelpMe()
{
MusicTrack track1 = new MusicTrack();
MusicTrack track2 = new MusicTrack();
// Set some values on the tracks
foreach (ITag tag in track1.Tags.Values)
tag.CopyFrom(false, track2.Tags[tag.TagName], true);
}
Here's one approach, which requires a decent amount of boilerplate but will allow you to do what you want using your existing definitions of ITag, ITag<T>, and Tag<T>. The TagSetter class sets the in memory value from the on disc value in a type safe way for any ITag<T>.
/// <summary>
/// Allows a tag of any type to be used to get a result of type TResult
/// </summary>
/// <typeparam name="TResult">The result type after using the tag</typeparam>
public interface ITagUser<TResult>
{
TResult Use<T>(ITag<T> tag);
}
/// <summary>
/// Allows a tag of any type to be used (with no return value)
/// </summary>
public interface ITagUser
{
void Use<T>(ITag<T> tag);
}
/// <summary>
/// Wraps a tag of some unknown type. Allows tag users (either with or without return values) to use the wrapped list.
/// </summary>
public interface IExistsTag
{
TResult Apply<TResult>(ITagUser<TResult> user);
void Apply(ITagUser user);
}
/// <summary>
/// Wraps a tag of type T, hiding the type itself.
/// </summary>
/// <typeparam name="T">The type of element contained in the tag</typeparam>
class ExistsTag<T> : IExistsTag
{
ITag<T> tag;
public ExistsTag(ITag<T> tag)
{
this.tag = tag;
}
#region IExistsTag Members
public TResult Apply<TResult>(ITagUser<TResult> user)
{
return user.Use(tag);
}
public void Apply(ITagUser user)
{
user.Use(tag);
}
#endregion
}
public interface ITag
{
string TagName { get; }
Type Type { get; }
}
public interface ITag<T> : ITag
{
T InMemValue { get; set; }
T OnDiscValue { get; set; }
}
public class Tag<T> : ITag<T>
{
public Tag(string tagName)
{
TagName = tagName;
}
public string TagName { get; private set; }
public T InMemValue { get; set; }
public T OnDiscValue { get; set; }
public Type Type { get { return typeof(T); } }
}
public class TagSetter : ITagUser
{
#region ITagUser Members
public void Use<T>(ITag<T> tag)
{
tag.InMemValue = tag.OnDiscValue;
}
#endregion
}
public class TagExtractor : ITagUser<ITag>
{
#region ITagUser<ITag> Members
public ITag Use<T>(ITag<T> tag)
{
return tag;
}
#endregion
}
public class MusicTrack
{
public MusicTrack()
{
TrackTitle = new Tag<string>("TrackTitle");
TrackNumber = new Tag<int>("TrackNumber");
Tags = new Dictionary<string, IExistsTag>();
Tags.Add(TrackTitle.TagName, new ExistsTag<string>(TrackTitle));
Tags.Add(TrackNumber.TagName, new ExistsTag<int>(TrackNumber));
}
public IDictionary<string, IExistsTag> Tags;
public ITag<string> TrackTitle { get; set; }
public ITag<int> TrackNumber { get; set; }
}
public static class Main
{
public static void DoSomething_WorksButNotIdeal()
{
MusicTrack track1 = new MusicTrack();
MusicTrack track2 = new MusicTrack();
TagSetter setter = new TagSetter();
TagExtractor extractor = new TagExtractor();
// Set some values on the tracks
foreach (IExistsTag tag in track1.Tags.Values)
{
tag.Apply(setter);
// do stuff using base interface if necessary
ITag itag = tag.Apply(extractor);
}
}
}