在C#中,我发现indexed properties非常有用。例如:
var myObj = new MyClass();
myObj[42] = "hello";
Console.WriteLine(myObj[42]);
但据我所知,没有语法糖来支持自己支持索引的字段(如果我错了请纠正我)。例如:
var myObj = new MyClass();
myObj.field[42] = "hello";
Console.WriteLine(myObj.field[42]);
我需要这个的原因是我已经在我的类上使用了index属性,但我有GetNumX()
,GetX()
和SetX()
函数,如下所示:
public int NumTargetSlots {
get { return _Maker.NumRefs; }
}
public ReferenceTarget GetTarget(int n) {
return ReferenceTarget.Create(_Maker.GetReference(n));
}
public void SetTarget(int n, ReferenceTarget rt) {
_Maker.ReplaceReference(n, rt._Target, true);
}
正如您可能会看到将这些作为一个可索引字段属性公开会更有意义。我可以编写一个自定义类来实现这一点,每次我想要语法糖,但所有的样板代码似乎都没有用。
因此,我编写了一个自定义类来封装样板,并且可以轻松创建可以编制索引的属性。这样我可以按如下方式添加新属性:
public IndexedProperty<ReferenceTarget> TargetArray {
get {
return new IndexedProperty<int, ReferenceTarget>(
(int n) => GetTarget(n),
(int n, ReferenceTarget rt) => SetTarget(n, rt));
}
}
这个新的IndexedProperty类的代码如下所示:
public class IndexedProperty<IndexT, ValueT>
{
Action<IndexT, ValueT> setAction;
Func<IndexT, ValueT> getFunc;
public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction)
{
this.getFunc = getFunc;
this.setAction = setAction;
}
public ValueT this[IndexT i]
{
get {
return getFunc(i);
}
set {
setAction(i, value);
}
}
}
所以我的问题是:是否有更好的方法来完成所有这些?
具体而言,在C#中是否有更为惯用的方法来创建可索引的字段属性,如果不是,我怎样才能改进我的IndexedProperty
类?
编辑:经过进一步研究,Jon Skeet称之为“named indexer”。
答案 0 :(得分:9)
这在技术上并不是使用StackOverflow的正确方法,但我发现你的想法非常有用,我对它进行了扩展。如果我发布了我想出的内容以及如何使用它的示例,我认为这会节省人们的时间。
首先,我需要能够支持get-only和set-only属性,所以我为这些场景略微改变了代码:
获取并设置(非常小的更改):
public class IndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
readonly Func<TIndex, TValue> GetFunc;
public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction)
{
this.GetFunc = getFunc;
this.SetAction = setAction;
}
public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
set
{
SetAction(i, value);
}
}
}
仅限获取:
public class ReadOnlyIndexedProperty<TIndex, TValue>
{
readonly Func<TIndex, TValue> GetFunc;
public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc)
{
this.GetFunc = getFunc;
}
public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
}
}
仅限设置:
public class WriteOnlyIndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction)
{
this.SetAction = setAction;
}
public TValue this[TIndex i]
{
set
{
SetAction(i, value);
}
}
}
示例强>
这是一个简单的用法示例。我继承了Collection并创建了一个命名索引器,就像Jon Skeet所说的那样。这个例子很简单,不实用:
public class ExampleCollection<T> : Collection<T>
{
public IndexedProperty<int, T> ExampleProperty
{
get
{
return new IndexedProperty<int, T>(GetIndex, SetIndex);
}
}
private T GetIndex(int index)
{
return this[index];
}
private void SetIndex(int index, T value)
{
this[index] = value;
}
}
Wild中的ExampleCollection
这个仓促构建的单元测试显示了在项目中的ExampleCollection时它的外观:
[TestClass]
public class IndexPropertyTests
{
[TestMethod]
public void IndexPropertyTest()
{
var MyExample = new ExampleCollection<string>();
MyExample.Add("a");
MyExample.Add("b");
Assert.IsTrue(MyExample.ExampleProperty[0] == "a");
Assert.IsTrue(MyExample.ExampleProperty[1] == "b");
MyExample.ExampleProperty[0] = "c";
Assert.IsTrue(MyExample.ExampleProperty[0] == "c");
}
}
最后,如果您想使用仅限get和only-only版本,则如下所示:
public ReadOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new ReadOnlyIndexedProperty<int, T>(GetIndex);
}
}
或者:
public WriteOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new WriteOnlyIndexedProperty<int, T>(SetIndex);
}
}
在这两种情况下,结果都与您期望只使用get / only-only属性的行为完全相同。
答案 1 :(得分:8)
嗯,最简单的方法是让属性返回一个实现IList
的对象。
请记住,仅仅因为它实现IList并不意味着它本身就是一个集合,只是它实现了某些方法。
答案 2 :(得分:7)
我认为你发布的设计是要走的路,我将定义一个界面的一个不同之处:
public interface IIndexed<IndexT, ValueT>
{
ValueT this[IndexT i] { get; set; }
}
对于常见情况,我会使用您在原始问题中放置的类(将实现此接口)。
如果基类库为我们提供了合适的接口会很好,但事实并非如此。在这里返回IList将是一种变态。
答案 3 :(得分:4)
这不能回答你的问题,但有趣的是要注意CIL支持制作你所描述的属性 - 某些语言(例如,F#)也允许你以这种方式定义它们。
C#中的this[]
索引器只是其中一个的特定实例,在您构建应用时会重命名为Item
。 C#编译器只知道如何读取这个,所以如果你在F#库中编写一个名为Target
的“命名索引器”,并尝试在C#中使用它,你可以访问该属性的唯一方法是通过... get_Target(int)
和void set_Target(int, ...)
方法。吸。
答案 4 :(得分:3)
为什么不让你的类继承IList然后你可以使用索引并为它添加自己的属性。虽然您仍然可以使用添加和删除功能,但不要使用它们。另外,你可能会觉得让它们继续发展是有用的。
有关列表和数组的更多信息,请查看: Which is better to use array or List<>?
修改强>
MSDN上有一篇关于您可能想要查看的索引属性的文章。似乎并不复杂,只是乏味。
http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx
还有另一个选项可以创建替代的Add方法,但根据对象的类型,可能无法始终调用add方法。在这里解释:
How do I override List<T>'s Add method in C#?
编辑2:与第一篇文章类似
为什么你的类中没有隐藏的列表对象,然后只是创建自己的方法来获取数据。这样就看不到添加和删除,列表已经编入索引。
你是什么意思“命名索引器”是你在寻找行[“My_Column_Name”]的等价物。我发现这篇MSDN文章可能很有用,因为它似乎显示了实现该属性的基本方法。
http://msdn.microsoft.com/en-us/library/146h6tk5.aspx
class Test
{
private List<T> index;
public T this[string name]{ get; set; }
public T this[int i]
{
get
{
return index[i];
}
set
{
index[i] = value;
}
}
}
答案 5 :(得分:2)
经过一番研究,我想出了一个稍微不同的解决方案,更符合我的需求。这个例子有点炮制,但它确实适合我需要它来适应它。
用法:
MyClass MC = new MyClass();
int x = MC.IntProperty[5];
使代码工作的代码:
public class MyClass
{
public readonly IntIndexing IntProperty;
public MyClass()
{
IntProperty = new IntIndexing(this);
}
private int GetInt(int index)
{
switch (index)
{
case 1:
return 56;
case 2:
return 47;
case 3:
return 88;
case 4:
return 12;
case 5:
return 32;
default:
return -1;
}
}
public class IntIndexing
{
private MyClass MC;
internal IntIndexing(MyClass mc)
{
MC = mc;
}
public int this[int index]
{
get { return MC.GetInt(index); }
}
}
}
答案 6 :(得分:0)
尝试显式实现的接口,如回复中提出的第二种方式所示: Named indexed property in C#?