来自资源文件的 C# 属性文本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1150874/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 09:26:48  来源:igfitidea点击:

C# attribute text from resource file?

c#resourcesattributes

提问by Patrick

I have an attribute and i want to load text to the attribute from a resource file.

我有一个属性,我想将文本从资源文件加载到该属性。

[IntegerValidation(1, 70, ErrorMessage = Data.Messages.Speed)]
private int i_Speed;

But I keep getting "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type"

但是我不断收到“属性参数必须是属性参数类型的常量表达式、typeof 表达式或数组创建表达式”

It works perfectly if i add a string instead of Data.Messages.Text, like:

如果我添加一个字符串而不是 Data.Messages.Text,它可以完美地工作,例如:

[IntegerValidation(1, 70, ErrorMessage = "Invalid max speed")]

Any ideas?

有任何想法吗?

采纳答案by Jon Skeet

Attribute values are hard-coded into the assembly when you compile. If you want to do anything at execution time, you'll need to use a constant as the key, then put some code into the attribute class itself to load the resource.

编译时,属性值被硬编码到程序集中。如果你想在执行时做任何事情,你需要使用一个常量作为,然后将一些代码放入属性类本身以加载资源。

回答by John Saunders

Use a string which is the name of the resource. .NET does this with some internal attributes.

使用作为资源名称的字符串。.NET 使用一些内部属性来做到这一点。

回答by Peter Lillevold

The nature of attributes is such that the data you put in attribute properties must be constants. These values will be stored within an assembly, but will never result in compiled code that is executed. Thus you cannot have attribute values that rely on being executed in order to calculate the results.

属性的本质是您放入属性属性中的数据必须是常量。这些值将存储在程序集中,但永远不会导致执行的已编译代码。因此,您不能拥有依赖于执行来计算结果的属性值。

回答by Peter Starbek

Here is my solution. I've added resourceName and resourceType properties to attribute, like microsoft has done in DataAnnotations.

这是我的解决方案。我已经将 resourceName 和 resourceType 属性添加到属性中,就像微软在 DataAnnotations 中所做的那样。

public class CustomAttribute : Attribute
{

    public CustomAttribute(Type resourceType, string resourceName)
    {
            Message = ResourceHelper.GetResourceLookup(resourceType, resourceName);
    }

    public string Message { get; set; }
}

public class ResourceHelper
{
    public static  string GetResourceLookup(Type resourceType, string resourceName)
    {
        if ((resourceType != null) && (resourceName != null))
        {
            PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static);
            if (property == null)
            {
                throw new InvalidOperationException(string.Format("Resource Type Does Not Have Property"));
            }
            if (property.PropertyType != typeof(string))
            {
                throw new InvalidOperationException(string.Format("Resource Property is Not String Type"));
            }
            return (string)property.GetValue(null, null);
        }
        return null; 
    }
}

回答by Peter

Here is the modified version of the one I put together:

这是我放在一起的修改版本:

[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)]
public class ProviderIconAttribute : Attribute
{
    public Image ProviderIcon { get; protected set; }

    public ProviderIconAttribute(Type resourceType, string resourceName)
    {
        var value = ResourceHelper.GetResourceLookup<Image>(resourceType, resourceName);

        this.ProviderIcon = value;
    }
}

    //From http://stackoverflow.com/questions/1150874/c-sharp-attribute-text-from-resource-file
    //Only thing I changed was adding NonPublic to binding flags since our images come from other dll's
    // and making it generic, as the original only supports strings
    public class ResourceHelper
    {
        public static T GetResourceLookup<T>(Type resourceType, string resourceName)
        {
            if ((resourceType != null) && (resourceName != null))
            {
                PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
                if (property == null)
                {
                    return default(T);
                }

                return (T)property.GetValue(null, null);
            }
            return default(T);
        }
    }

回答by Words Like Jared

Here's something I wrote since I couldn't find anything else that does this.:

这是我写的东西,因为我找不到其他任何可以做到这一点的东西。:

Input

输入

Write a constant string class in project A.

在项目A中编写一个常量字符串类。

[GenerateResource]
public static class ResourceFileName
{
    public static class ThisSupports
    {
        public static class NestedClasses
        {
            [Comment("Comment value")]
            public const string ResourceKey = "Resource Value";
        }
    }
}

Output

输出

And a resource will be generated in the project that contains the constants class.

并且会在包含常量类的项目中生成一个资源。

enter image description here

在此处输入图片说明

All you need to do is have this code somewhere:

您需要做的就是在某处放置此代码:

Source

来源

public class CommentAttribute : Attribute
{
    public CommentAttribute(string comment)
    {
        this.Comment = comment;
    }

    public string Comment { get; set; }
}

public class GenerateResourceAttribute : Attribute
{
    public string FileName { get; set; }
}

public class ResourceGenerator
{
    public ResourceGenerator(IEnumerable<Assembly> assemblies)
    {
        // Loop over the provided assemblies.
        foreach (var assembly in assemblies)
        {
            // Loop over each type in the assembly.
            foreach (var type in assembly.GetTypes())
            {
                // See if the type has the GenerateResource attribute.
                var attribute = type.GetCustomAttribute<GenerateResourceAttribute>(false);
                if (attribute != null)
                {
                    // If so determine the output directory.  First assume it's the current directory.
                    var outputDirectory = Directory.GetCurrentDirectory();

                    // Is this assembly part of the output directory?
                    var index = outputDirectory.LastIndexOf(typeof(ResourceGenerator).Assembly.GetName().Name);
                    if (index >= 0)
                    {
                        // If so remove it and anything after it.
                        outputDirectory = outputDirectory.Substring(0, index);

                        // Is the concatenation of the output directory and the target assembly name not a directory?
                        outputDirectory = Path.Combine(outputDirectory, type.Assembly.GetName().Name);
                        if (!Directory.Exists(outputDirectory))
                        {
                            // If that is the case make it the current directory. 
                            outputDirectory = Directory.GetCurrentDirectory();
                        }
                    }

                    // Use the default file name (Type + "Resources") if one was not provided.
                    var fileName = attribute.FileName;
                    if (fileName == null)
                    {
                        fileName = type.Name + "Resources";
                    }

                    // Add .resx to the end of the file name.
                    fileName = Path.Combine(outputDirectory, fileName);
                    if (!fileName.EndsWith(".resx", StringComparison.InvariantCultureIgnoreCase))
                    {
                        fileName += ".resx";
                    }

                    using (var resx = new ResXResourceWriter(fileName))
                    {
                        var tuples = this.GetTuplesRecursive("", type).OrderBy(t => t.Item1);
                        foreach (var tuple in tuples)
                        {
                            var key = tuple.Item1 + tuple.Item2.Name;

                            var value = tuple.Item2.GetValue(null);

                            string comment = null;
                            var commentAttribute = tuple.Item2.GetCustomAttribute<CommentAttribute>();
                            if (commentAttribute != null)
                            {
                                comment = commentAttribute.Comment;
                            }

                            resx.AddResource(new ResXDataNode(key, value) { Comment = comment });
                        }
                    }
                }
            }
        }
    }

    private IEnumerable<Tuple<string, FieldInfo>> GetTuplesRecursive(string prefix, Type type)
    {
        // Get the properties for the current type.
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
        {
            yield return new Tuple<string, FieldInfo>(prefix, field);
        }

        // Get the properties for each child type.
        foreach (var nestedType in type.GetNestedTypes())
        {
            foreach (var tuple in this.GetTuplesRecursive(prefix + nestedType.Name, nestedType))
            {
                yield return tuple;
            }
        }
    }
}

And then make a small project that has a reference to all your assemblies with [GenerateResource]

然后创建一个小项目,其中包含对所有程序集的引用 [GenerateResource]

public class Program
{
    static void Main(string[] args)
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
        string path = Directory.GetCurrentDirectory();
        foreach (string dll in Directory.GetFiles(path, "*.dll"))
        {
            assemblies.Add(Assembly.LoadFile(dll));
        }
        assemblies = assemblies.Distinct().ToList();

        new ResourceGenerator(assemblies);
    }
}

Then your attributes can use the static class ResourceFileName.ThisSupports.NestedClasses.ResourceKeywhile other code can use the resource file.

然后你的属性可以使用静态类,ResourceFileName.ThisSupports.NestedClasses.ResourceKey而其他代码可以使用资源文件。

You might need to tailor it to your specific needs.

您可能需要根据您的特定需求对其进行定制。

回答by Steve Andrews

I have a similar case, where I need to put resource strings into attributes. In C# 6, we have the nameof()capability, and that seems to do the trick.

我有一个类似的案例,我需要将资源字符串放入属性中。在 C# 6 中,我们有nameof()能力,这似乎可以解决问题。

In my case, I can use [SomeAttribute(nameof(Resources.SomeResourceKey))]and it compiles fine. Then I just have to do a little work on the other end to use that value to get the correct string from the Resources file.

就我而言,我可以使用[SomeAttribute(nameof(Resources.SomeResourceKey))]并且编译得很好。然后我只需要在另一端做一些工作来使用该值从资源文件中获取正确的字符串。

In your case, you might try:

在您的情况下,您可以尝试:

[IntegerValidation(1, 70, ErrorMessageResourceKey = nameof(Data.Messages.Speed))]
private int i_Speed;

Then you can do something along the lines of (pseudo code):

然后你可以按照(伪代码)的方式做一些事情:

Properties.Resources.ResourceManager.GetString(attribute.ErrorMessageResourceKey);

回答by campo

I came across this problem with the display name for attribute, and I made the following changes:

我在属性的显示名称中遇到了这个问题,并进行了以下更改:

For our resource file I changed the custom tool property to PublicResXFileCodeGenerator

对于我们的资源文件,我将自定义工具属性更改为 PublicResXFileCodeGenerator

Then added this to the attribute:

然后将其添加到属性中:

[Display(Name = "MyResourceName", ResourceType = typeof(Resources.MyResources))]

回答by Bahram Ghahari

If you're using .NET 3.5 or newer you can use ErrorMessageResourceNameand ErrorMessageResourceTypeparameters.

如果您使用 .NET 3.5 或更高版本,则可以使用ErrorMessageResourceNameErrorMessageResourceType参数。

For example [Required(ErrorMessageResourceName ="attribute_name" , ErrorMessageResourceType = typeof(resource_file_type))]

例如 [Required(ErrorMessageResourceName ="attribute_name" , ErrorMessageResourceType = typeof(resource_file_type))]